commit 70323168b1d660f7b7884b918b77b31b35bdc705 Author: alpcentaur Date: Thu Apr 15 13:09:24 2021 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..a09a495 --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +*** + ____ _ _ ____ _____ +/ ___|| \ | |/ ___| ___| +\___ \| \| | | | |_ + ___) | |\ | |___| _| +|____/|_| \_|\____|_| + + ____ _ ____ +| _ \ ___ ___| | _____ _ __ / ___|___ _ __ ___ _ __ ___ ___ ___ +| | | |/ _ \ / __| |/ / _ \ '__|____| | / _ \| '_ ` _ \| '_ \ / _ \/ __|/ _ \ +| |_| | (_) | (__| < __/ | |_____| |__| (_) | | | | | | |_) | (_) \__ \ __/ +|____/ \___/ \___|_|\_\___|_| \____\___/|_| |_| |_| .__/ \___/|___/\___| + |_| +*** + +## Setting up the Nextcloud containers + +# Changes in docker/compose/docker-compose.yml + +Go to docker-compose.yml and change all AAAAApw to your passwords. + +Then change example.org to your domain. + +# Install and start docker compose + +Point your domain to the public ip of your server. + +Enter the command to start dockerd after installing docker on your host system: +```bash + sudo systemctl start docker +``` +Install docker-compose on your host os and in the folder docker/compose do: +```bash + sudo docker-compose up +``` + +## Setting up nextcloud with deb-rust-sncf container + +# Configure Nextcloud in the admin panel like in the SNCF Doku + +Then open example.org in your favourite browser, +create admin account, uncheck install recommended apps. + +Do everything as described in sncf (uncheck things in settings, uninstall +everything except forms, apporder) except the alternative custom css if you want +a save button that does nothing than reload (nextcloud does already save realtime) +but the realtime saving could be confusing for some users. + +custom css code for save button: +```CSS + #contactsmenu { + display: none !important; +} + + +#appmenu { + + visibility: hidden; + +} + +#appmenu li:hover a, #appmenu li a:focus { + font-size: 12px; +} + +#appmenu li span { + visibility: hidden; + background-color: yellow; + +} + +#appmenu li svg { + visibility: hidden; + background-color: yellow; + +} + + + +#appmenu li a:last-child::after { + + + content: "Save"; + #padding: 10px; + #margin: 10px; + height: 50%; + width: 200%; + padding-top: 7px; + padding-right: 6px; + #padding-bottom: 10px; + padding-left: 10px; + visibility: visible; + background-color: rgba(237, 237, 237, .7); + color: black; + #text-shadow: 0px 0px 8px black; + font-family: "Mono", Courier, monospace; + font-weight: bold; + border: 1px solid rgba(237, 237, 237, .7); + border-radius: 15px; + +} + +#appmenu li a:last-child:hover:after { + + background-color: rgba(237, 237, 237, 0.7); + border: 1px solid rgba(77, 77, 77, 0.3); + #background-color: #fbc617; + visibility: visible; + +} + +#appmenu li a:last-child:focus:after { + + content: "✓Save"; + background-color: rgba(237, 237, 237, 0.7); + border: 1px solid rgba(77, 77, 77, 0.3); + #background-color: #fbc617; + visibility: visible; + +} + + + +#settings { + display:none !important; +} + +.app-sidebar-tabs__content > ul:nth-child(2) { + display:none !important; +} + +.app-sidebar-tabs__content > ul:nth-child(4) > li:nth-child(1) { + display:none !important; +} + +.app-sidebar-tabs__content > ul:nth-child(4) > li:nth-child(2) { + display:none !important; +} + +``` + +# Change nextcloud config for deb-rust-sncf + + +then changes (with your domain) in config.php +(/opt/docker/overlays/nextcloud/html/config): + +```PHP + + 'simpleSignUpLink.shown' => false + 'defaultapp' => 'apporder' + 'trusted_domains' => + array ( + 0 => 'nextcloud-web', + 1 => 'example.org', + ), +'trusted_proxies' => ['traefik', 'deb-rust-sncf'], +'overwrite.cli.url' => 'http://example.org', + +``` +get the updated config.php in your nextcloud instance with the following command: +```bash + docker exec -u www-data nextcloud php occ files:scan --all +``` + +# Change nextcloud definition in compose file and uncomment deb-rust-sncf + +Then open the docker-compose.yml file, comment all labels of the container +nextcloud-web. + +Now uncomment the whole deb-rust-sncf container. + +If you want to customize the landing or link page, some files are exposed already +(and then copied in the container during build) in the folder build/deb-rust-sncf + +Regarding the sncf proxy, the files link.html and forward.rs have changes. +These changes give the functionality to send the admin link to users mail. +Until now, the post http request and the mail adress are saved in a file on the +container deb-rust-sncf. + +# Configure rust build withing deb-rust-sncf + +Go to docker/build/deb-rust-sncf anyway and make changes in config.toml according +the sncf wiki. +Change the passwords AAAAA and example.org to your domain. + + +# Remove and restart all containers + +Now stop all containers with either issuing +```bash + sudo docker-compose stop +``` +or pressing `Ctrl-C` + +Delete the WHOLE network with the command +```bash + sudo docker system prune --all +``` +Then restart with +```bash + sudo docker-compose up +``` +in the folder docker/compose + + diff --git a/docker/build/deb-rust-sncf/Dockerfile b/docker/build/deb-rust-sncf/Dockerfile new file mode 100644 index 0000000..8acfab2 --- /dev/null +++ b/docker/build/deb-rust-sncf/Dockerfile @@ -0,0 +1,42 @@ +FROM rust:slim + +WORKDIR /opt + +# Install needed dependecies + +RUN echo "deb http://ftp.de.debian.org/debian unstable main contrib" | tee -a /etc/apt/sources.list + +RUN apt-get update && apt-get install -y libmysql++-dev git + +RUN git clone https://git.42l.fr/neil/sncf.git + +WORKDIR /opt/sncf + +COPY config.toml /opt/sncf/config.toml + +# graphics individualization + +COPY Digi_3corner_up.png /opt/sncf/templates/assets/logo.svg + +COPY white-background.png /opt/sncf/templates/assets/index-background.png + +COPY Digi_3corner.png /opt/sncf/templates/assets/flavicon.ico + +COPY index.css /opt/sncf/templates/assets/index.css + +COPY index.html /opt/sncf/templates/index.html + +COPY link.html /opt/sncf/templates/link.html + +COPY forward.rs /opt/sncf/src/forward.rs + +#COPY templates.rs /opt/sncf/src/templates.rs + +# The written is just firstly a hack + +COPY lang.json /opt/sncf/lang.json + +CMD cargo run --no-default-features --features mysql + + + diff --git a/docker/build/deb-rust-sncf/config.toml b/docker/build/deb-rust-sncf/config.toml new file mode 100644 index 0000000..efdcba2 --- /dev/null +++ b/docker/build/deb-rust-sncf/config.toml @@ -0,0 +1,34 @@ +# The address and port sncf will listen +listening_address = "0.0.0.0" +listening_port = 8000 + +# Public-facing domain for sncf. +# includes protocol, FQDN and port, without the trailing slash. +sncf_url = "http://example.org" + +# SQLite: path to the SQLite DB +# PostgreSQL: postgres://user:password@address:port/database +# MySQL: mysql://user:password@address:port/database +database_path = "mysql://nextcloud:AAAAAAAAAAAAAAAAAAAAAAAAAAAAMYSQL@nextcloud-db:3306/nextcloud" + +# IP address of the Nextcloud instance, including protocol and port +nextcloud_url = "http://nextcloud-web:80" + +# Nextcloud admin account credentials +# TODO hash adminpw +admin_username = "AAAAAAAAAAAusername" +admin_password = "AAAAAAAAAadminpw" + +# How many days of inactivity for an admin token before deleting NC accounts +prune_days = 40 + +# Displays route names and a lot of information +debug_mode = true + +# Used to encrypt csrf tokens and csrf cookies. +# Generate random bytes: openssl rand -base64 32 +# Then paste the result in this variable +cookie_key = "Af3v5KMNPmwYYBRRjm/W5ds1rHDdyCEvpxVTMLKEKl0=" + +# Don't touch this unless you know what you're doing +config_version = 2 diff --git a/docker/build/deb-rust-sncf/forward.rs b/docker/build/deb-rust-sncf/forward.rs new file mode 100644 index 0000000..4baf9da --- /dev/null +++ b/docker/build/deb-rust-sncf/forward.rs @@ -0,0 +1,438 @@ +use actix_web::client::{Client, ClientRequest}; +use actix_web::{http, web, HttpRequest, HttpResponse}; +use askama::Template; +use chrono::Utc; +use regex::Regex; +use std::time::Duration; +use url::Url; +use csrf::{AesGcmCsrfProtection, CsrfProtection}; + +use crate::config::get_csrf_key; +use crate::account::*; +use crate::config::PAYLOAD_LIMIT; +use crate::config::PROXY_TIMEOUT; +use crate::database::methods::InsertableForm; +use crate::database::structs::Form; +use crate::debug; +use crate::errors::{crash, TrainCrash}; +use crate::sniff::*; +use crate::templates::*; +use crate::DbPool; +use crate::CONFIG; + +pub async fn forward( + req: HttpRequest, + body: web::Bytes, + url: web::Data, + client: web::Data, +) -> Result { + let route = req.uri().path(); + + + if route == "/link/email" { + let email_request_headers = &req; + let email_body = &body; + + + //let mut body = String::new(); + + let forged_emailheaders = format!( + "{:?}", + email_request_headers + ); + let forged_emailbody = format!( + "{:?}", + email_body + ); + //let body = email_response_body.escape_ascii().to_string(); + + /*email_response_body.read_to_string(&mut body)?;*/ + // does not work, because body is in bytes format. + println!("da budy {}",forged_emailbody ); + println!("da headers {}",forged_emailheaders); + + use std::io::Write; + use std::fs::OpenOptions; + let mut f = OpenOptions::new() + .append(true) + .create(true) // Optionally create the file if it doesn't already exist + .open("/tmp/foo") + .expect("Unable to open file"); + + //let body_bytes = as_bytes!("{:?}", body); + + f.write_all(forged_emailheaders.as_bytes()).expect("Unable to write data"); + f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data"); + + + } + + + // if check_route returns true, + // the user supposedly tried to access a restricted page. + // They get redirected to the main page. + if check_route(route) { + debug(&format!("Restricted route blocked: {}", route)); + return Ok(web_redir("/").await.map_err(|e| { + eprintln!("error_redirect: {}", e); + crash(get_lang(&req), "error_redirect") + })?); + } + + let forwarded_req = forge_from(route, &req, &url, &client); + + // check the request before sending it + // (prevents the user from sending some specific POST requests) + if check_request(route, &body) { + debug(&format!( + "Restricted request: {}", + String::from_utf8_lossy(&body) + )); + return Err(crash(get_lang(&req), "error_dirtyhacker")); + } + + // send the request to the Nextcloud instance + let mut res = forwarded_req.send_body(body).await.map_err(|e| { + eprintln!("error_forward_resp: {}", e); + crash(get_lang(&req), "error_forward_req") + })?; + + let mut client_resp = HttpResponse::build(res.status()); + // remove connection as per the spec + // and content-encoding since we have to decompress the traffic to edit it + // and basic-auth, because this feature is not needed. + for (header_name, header_value) in res + .headers() + .iter() + .filter(|(h, _)| *h != "connection" && *h != "content-encoding") + { + client_resp.header(header_name.clone(), header_value.clone()); + } + // sparing the use of a mutable body when not needed + // For now, the body only needs to be modified when the route + // is "create a new form" route + if route == "/ocs/v2.php/apps/forms/api/v1/form" { + // retreive the body from the request result + let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| { + eprintln!("error_forward_resp: {}", e); + crash(get_lang(&req), "error_forward_resp") + })?; + + // if a new form is created, automatically set some fields. + // this is very hackish but it works! for now. + let form_id = check_new_form(&response_body); + if form_id > 0 { + debug(&format!( + "New form. Forging request to set isAnonymous for id {}", + form_id + )); + + let forged_body = format!( + r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#, + form_id + ); + let update_req = forge_from( + "/ocs/v2.php/apps/forms/api/v1/form/update", + &req, + &url, + &client, + ) + .set_header("content-length", forged_body.len()) + .set_header("content-type", "application/json;charset=utf-8"); + + let res = update_req.send_body(forged_body).await.map_err(|e| { + eprintln!("error_forward_isanon: {}", e); + crash(get_lang(&req), "error_forward_isanon") + })?; + debug(&format!("(new_form) Request returned {}", res.status())); + } + Ok(client_resp.body(response_body).await.map_err(|e| { + eprintln!("error_forward_clientresp_newform: {}", e); + crash(get_lang(&req), "error_forward_clientresp_newform") + })?) + } else { + Ok( + client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| { + eprintln!("error_forward_clientresp_newform: {}", e); + crash(get_lang(&req), "error_forward_clientresp_std") + })?), + ) + } + + // check the response before returning it (unused) + /*if check_response(route, &response_body) { + return Ok(web_redir("/")); + }*/ +} + +#[derive(Deserialize)] +pub struct LoginToken { + pub token: String, +} + +#[derive(Deserialize)] +pub struct CsrfToken { + pub csrf_token: String, +} + +pub async fn forward_login( + req: HttpRequest, + params: web::Path, + client: web::Data, + dbpool: web::Data, +) -> Result { + // if the user is already logged in, redirect to the Forms app + if is_logged_in(&req).is_some() { + return Ok(web_redir("/apps/forms").await.map_err(|e| { + eprintln!("error_redirect (1:/apps/forms/): {}", e); + crash(get_lang(&req), "error_redirect") + })?); + } + + // check if the provided token seems valid. If not, early return. + if !check_token(¶ms.token) { + debug("Incorrect admin token given in params."); + debug(&format!("Token: {:#?}", params.token)); + return Err(crash(get_lang(&req), "error_dirtyhacker")); + } + + let conn = dbpool.get().map_err(|e| { + eprintln!("error_forwardlogin_db: {}", e); + crash(get_lang(&req), "error_forwardlogin_db") + })?; + + // check if the link exists in DB. if it does, update lastvisit_at. + let formdata = web::block(move || Form::get_from_token(¶ms.token, &conn)) + .await + .map_err(|e| { + eprintln!("error_forwardlogin_db_get (diesel error): {}", e); + crash(get_lang(&req), "error_forwardlogin_db_get") + })? + .ok_or_else(|| { + debug("Token not found."); + crash(get_lang(&req), "error_forwardlogin_notfound") + })?; + + // else, try to log the user in with DB data, then redirect. + login(&client, &req, &formdata.nc_username, &formdata.nc_password).await +} + + + + +// creates a NC account using a random name and password. +// the account gets associated with a token in sqlite DB. +pub async fn forward_register( + req: HttpRequest, + csrf_post: web::Form, + client: web::Data, + dbpool: web::Data, +) -> Result { + let lang = get_lang(&req); + + // if the user is already logged in, redirect to the Forms app + if is_logged_in(&req).is_some() { + return Ok(web_redir("/apps/forms").await.map_err(|e| { + eprintln!("error_redirect (2:/apps/forms/): {}", e); + crash(get_lang(&req), "error_redirect") + })?); + } + + // if the user has already generated an admin token, redirect too + if let Some(token) = has_admintoken(&req) { + lazy_static! { + static ref RE: Regex = Regex::new(r#"sncf_admin_token=(?P[0-9A-Za-z_\-]*)"#) + .expect("Error while parsing the sncf_admin_token regex"); + } + let admin_token = RE + .captures(&token) + .ok_or_else(|| { + eprintln!("error_forwardregister_tokenparse (no capture)"); + crash(get_lang(&req), "error_forwardregister_tokenparse") + })? + .name("token") + .ok_or_else(|| { + eprintln!("error_forwardregister_tokenparse (no capture named token)"); + crash(get_lang(&req), "error_forwardregister_tokenparse") + })? + .as_str(); + // sanitize the token beforehand, cookies are unsafe + if check_token(&admin_token) { + return Ok( + web_redir(&format!("{}/admin/{}", CONFIG.sncf_url, &admin_token)) + .await + .map_err(|e| { + eprintln!("error_redirect (admin): {}", e); + crash(get_lang(&req), "error_redirect") + })?, + ); + } else { + debug("Incorrect admin token given in cookies."); + debug(&format!("Token: {:#?}", &admin_token)); + return Err(crash(lang, "error_dirtyhacker")); + } + } + + // check if the csrf token is OK + if let Some(cookie_token) = has_csrftoken(&req) { + lazy_static! { + static ref RE: Regex = Regex::new(r#"sncf_csrf_cookie=(?P[0-9A-Za-z_\-]*)"#) + .expect("Error while parsing the sncf_csrf_cookie regex"); + } + let cookie_csrf_token = RE + .captures(&cookie_token) + .ok_or_else(|| { + eprintln!("error_csrf_cookie: no capture"); + crash(get_lang(&req), "error_csrf_cookie") + })? + .name("token") + .ok_or_else(|| { + eprintln!("error_csrf_cookie: no capture named token"); + crash(get_lang(&req), "error_csrf_cookie") + })? + .as_str(); + + let raw_ctoken = base64::decode_config(cookie_csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| { + eprintln!("error_csrf_cookie (base64): {}", e); + crash(get_lang(&req), "error_csrf_cookie") + })?; + + let raw_token = base64::decode_config(csrf_post.csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| { + eprintln!("error_csrf_token (base64): {}", e); + crash(get_lang(&req), "error_csrf_token") + })?; + + let seed = AesGcmCsrfProtection::from_key(get_csrf_key()); + let parsed_token = seed.parse_token(&raw_token).expect("token not parsed"); + let parsed_cookie = seed.parse_cookie(&raw_ctoken).expect("cookie not parsed"); + if !seed.verify_token_pair(&parsed_token, &parsed_cookie) { + debug("warn: CSRF token doesn't match."); + return Err(crash(lang, "error_csrf_token")); + } + } + else { + debug("warn: missing CSRF token."); + return Err(crash(lang, "error_csrf_cookie")); + } + + let nc_username = gen_name(); + println!("gen_name: {}", nc_username); + let nc_password = gen_token(45); + // attempts to create the account + create_account(&client, &nc_username, &nc_password, lang.clone()).await?; + + debug(&format!("Created user {}", nc_username)); + + let conn = dbpool.get().map_err(|e| { + eprintln!("error_forwardregister_pool: {}", e); + crash(lang.clone(), "error_forwardregister_pool") + })?; + + let token = gen_token(45); + + let token_mv = token.clone(); + + // store the result in DB + let form_result = web::block(move || Form::insert( + InsertableForm { + created_at: Utc::now().naive_utc(), + lastvisit_at: Utc::now().naive_utc(), + token: token_mv, + nc_username, + nc_password, + }, + &conn, + )) + .await; + + if form_result.is_err() { + return Err(crash(lang, "error_forwardregister_db")); + } + + Ok(HttpResponse::Ok() + .content_type("text/html") + .set_header( + "Set-Cookie", + format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token), + ) + .body( + TplLink { + lang: &lang, + admin_token: &token, + config: &CONFIG, + } + .render() + .map_err(|e| { + eprintln!("error_tplrender (TplLink): {}", e); + crash(lang.clone(), "error_tplrender") + })?, + ) + .await + .map_err(|e| { + eprintln!("error_tplrender_resp (TplLink): {}", e); + crash(lang, "error_tplrender_resp") + })?) +} + +// create a new query destined to the nextcloud instance +// needed to forward any query +fn forge_from( + route: &str, + req: &HttpRequest, + url: &web::Data, + client: &web::Data, +) -> ClientRequest { + let mut new_url = url.get_ref().clone(); + new_url.set_path(route); + new_url.set_query(req.uri().query()); + + // insert forwarded header if we can + let mut forwarded_req = client + .request_from(new_url.as_str(), req.head()) + .timeout(Duration::new(PROXY_TIMEOUT, 0)); + + // attempt to remove basic-auth header + forwarded_req.headers_mut().remove("authorization"); + if let Some(addr) = req.head().peer_addr { + forwarded_req.header("x-forwarded-for", format!("{}", addr.ip())) + } else { + forwarded_req + } +} + +fn web_redir(location: &str) -> HttpResponse { + HttpResponse::SeeOther() + .header(http::header::LOCATION, location) + .finish() +} + +pub async fn index(req: HttpRequest) -> Result { + + let seed = AesGcmCsrfProtection::from_key(get_csrf_key()); + let (csrf_token, csrf_cookie) = seed.generate_token_pair(None, 43200) + .expect("couldn't generate token/cookie pair"); + + Ok(HttpResponse::Ok() + .content_type("text/html") + .set_header( + "Set-Cookie", + format!("sncf_csrf_cookie={}; HttpOnly; SameSite=Strict", + base64::encode_config(&csrf_cookie.value(), base64::URL_SAFE_NO_PAD))) + .body( + TplIndex { + lang: &get_lang(&req), + csrf_token: &base64::encode_config(&csrf_token.value(), base64::URL_SAFE_NO_PAD), + } + .render() + .map_err(|e| { + eprintln!("error_tplrender (TplIndex): {}", e); + crash(get_lang(&req), "error_tplrender") + })?, + ) + .await + .map_err(|e| { + eprintln!("error_tplrender_resp (TplIndex): {}", e); + crash(get_lang(&req), "error_tplrender_resp") + })?) +} + diff --git a/docker/build/deb-rust-sncf/index.css b/docker/build/deb-rust-sncf/index.css new file mode 100644 index 0000000..2475ab7 --- /dev/null +++ b/docker/build/deb-rust-sncf/index.css @@ -0,0 +1,281 @@ +@font-face { + font-family: 'Ubuntu-R'; + src: url('/assets/Ubuntu-R.ttf'); + font-weight: normal; + font-style: normal; +} + +.hidden { + display: none !important; +} + +* { + font-family: Ubuntu,"Ubuntu-R",sans-serif; +} + +a { + text-decoration: none; + color: #2359fb; +} + +.flex { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.fullheight { + min-height: 100vh; +} + +.fullheight-nav { + min-height: calc(100vh - 50px); +} + +.fullwidth { + width: 100%; + text-align: center; +} + +.title { + color: white; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18); +} + +h1 { + font-size: 4vw; +} + +h2 { + font-size: 2.25vw; +} + +h3 { + font-size: 1.75vw; +} + +p { + font-size: 1.25vw; + line-height: 1.6; +} + +.beta-tag { + background: #f47606; + color: white; + border-radius: 5px; + font-size: 0.9rem; + padding: 0.3rem; + margin-left: 0.5rem; +} +.beta-banner a { + color: #ffeb7f; +} + +.beta-banner { + background: repeating-linear-gradient( 45deg, #d56009, #d56009 10px, #c44c05 10px, #c44c05 20px ); + color: white; + padding: 1rem; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18); +} + +.logo { + width: 10vw; + margin-right: 2vw; +} + +.page-heading { + background-image: url("/assets/index-background.png"), linear-gradient(0deg, #1f58c6 0%, #1c66f2 100%); + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; +} + +.page-heading-text { + width: auto; + margin: auto; + padding: 1rem; + +} + +.page-heading > p { + color: white; +} + +.page-heading > p > a { + color: #c3cce8; +} + +.page-heading.error { + background: url("/assets/index-background.png"), linear-gradient(0deg, #790000 0%, #a40000 100%) +} + +.ncstyle-button.error { + background: #ee4040; +} + +.error.ncstyle-button:hover { + background: #c82323; +} + +.navbar { + height: 50px; +} + +body, html { + margin: 0; + padding: 0; +} + +.ncstyle-button { + color: #FFF; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18); + border-radius: 1vw; + text-decoration: none; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18); + white-space: nowrap; + height: max-content; + line-height: 2.25rem; + padding: 2vh 3vw; + background: #2a87ff; + font-size: 1.5vw; + min-width: 18vw; + display: block; + transition: all .25s ease-in-out; +} + +.margin-bottom { + margin-bottom: 1rem; +} + +.ncstyle-button:hover { + background: #2478e3; +} + +.ncstyle-input { + margin: auto; + padding: 7px 6px; + font-size: 16px; + background-color: white; + color: #454545; + border: 1px solid #dbdbdb; + outline: none; + border-radius: 3px; + cursor: text; + width: 50vw; +} + +.click { + cursor: pointer; +} + +#script-copy { + display: none; +} + +@media only screen and (max-width: 1080px) { + h1 { + font-size: 48px; + } + + h2 { + font-size: 32px; + } + + h3 { + font-size: 24px; + } + + p { + font-size: 16px; + } + + + .title { + text-align: center; + } + + .logo { + width: 20vw; + margin: 0; + } + + .ncstyle-button { + font-size: 24px; + } +} + +@media only screen and (max-width: 1080px), screen and (max-height: 600px) { + .scroll-down-arrow { + display: none; + } +} + +.scroll-down-arrow { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2hldnJvbl90aGluX2Rvd24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwIDIwIiBmaWxsPSJ3aGl0ZSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTE3LjQxOCw2LjEwOWMwLjI3Mi0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwYzAuMjcsMC4yNjgsMC4yNzEsMC43MDEsMCwwLjk2OWwtNy45MDgsNy44M2MtMC4yNywwLjI2OC0wLjcwNywwLjI2OC0wLjk3OSwwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEsMC0wLjk2OWMwLjI3MS0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwTDEwLDEzLjI1TDE3LjQxOCw2LjEwOXoiLz48L3N2Zz4=); + background-size: contain; + background-repeat: no-repeat; +} + +.scroll-down-link { + cursor:pointer; + height: 60px; + width: 80px; + margin: 0px 0 0 -40px; + line-height: 60px; + position: absolute; + left: 50%; + bottom: 10px; + color: #FFF; + text-align: center; + font-size: 70px; + z-index: 100; + text-decoration: none; + text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.4); + animation: fade_move_down 2s ease-in-out infinite; +} + +/*animated scroll arrow animation*/ +@keyframes fade_move_down { + 0% { transform:translate(0,-20px); opacity: 0; } + 50% { opacity: 1; } + 100% { transform:translate(0,20px); opacity: 0; } +} + + +.lds-ring { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} +.lds-ring div { + box-sizing: border-box; + display: block; + position: absolute; + width: 64px; + height: 64px; + margin: 8px; + border: 8px solid #fff; + border-radius: 50%; + animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #fff transparent transparent transparent; +} +.lds-ring div:nth-child(1) { + animation-delay: -0.45s; +} +.lds-ring div:nth-child(2) { + animation-delay: -0.3s; +} +.lds-ring div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes lds-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + diff --git a/docker/build/deb-rust-sncf/index.html b/docker/build/deb-rust-sncf/index.html new file mode 100644 index 0000000..39d7321 --- /dev/null +++ b/docker/build/deb-rust-sncf/index.html @@ -0,0 +1,148 @@ + + + + {{ "index_title"|tr(lang) }} – {{ "index_description"|tr(lang) }} + + + + + + + + + + + +
+
+
+ +
+
+

{{ "index_title"|tr(lang) }}{{ "index_beta_tag"|tr(lang) }}

+

{{ "index_description"|tr(lang) }}

+
+
+
+
+ + +
+
+

{{ "index_beta_banner_desc1"|tr(lang) }}

+

{{ "index_beta_banner_desc2"|tr(lang) }}{{ "index_beta_banner_desc_link"|tr(lang) }}.

+
+
+
+
+
+
+
+

{{ "index_disclaimer1"|tr(lang) }}

+

{{ "index_disclaimer2"|tr(lang) }}{{ "index_disclaimer2_link_org"|tr(lang) }}{{ "index_disclaimer2_or"|tr(lang) }}{{ "index_disclaimer2_nc"|tr(lang) }}.

+
+
+
+
+
+
+ +
+
+

{{ "index_panel1_title"|tr(lang) }}

+

{{ "index_panel1_desc1"|tr(lang) }}

+

{{ "index_panel1_desc2"|tr(lang) }}

+
+
+
+
+ +
+
+

{{ "index_panel2_title"|tr(lang) }}

+

{{ "index_panel2_desc1"|tr(lang) }}

+

{{ "index_panel2_desc2"|tr(lang) }}{{ "index_panel2_desc2_link"|tr(lang) }}.

+
+
+
+
+ +
+
+

{{ "index_panel3_title"|tr(lang) }}

+

{{ "index_panel3_desc1"|tr(lang) }}

+
+
+
+
+ +
+
+

{{ "index_panel4_title"|tr(lang) }}

+

{{ "index_panel4_desc1"|tr(lang) }}

+
+
+
+
+ +
+
+

{{ "index_panel5_title"|tr(lang) }}

+

{{ "index_panel5_desc1"|tr(lang) }}

+

{{ "index_panel5_desc2"|tr(lang) }}

+
+
+
+
+ +
+
+

{{ "index_panel6_title"|tr(lang) }}

+

{{ "index_panel5_desc1"|tr(lang) }}

+
+
+
+
+ +
+
+
+

Crédits

+

{{ "index_credits_desc1"|tr(lang) }}{{ "index_credits_desc1_link"|tr(lang) }}{{ "index_credits_desc1_a"|tr(lang) }}

+

{{ "index_credits_desc2"|tr(lang) }}Neil{{ "index_credits_desc2_for"|tr(lang) }}{{ "index_credits_desc2_org"|tr(lang) }} ({{"index_credits_desc3"|tr(lang) }}).

+
+
+ + + diff --git a/docker/build/deb-rust-sncf/link.html b/docker/build/deb-rust-sncf/link.html new file mode 100644 index 0000000..422b4f9 --- /dev/null +++ b/docker/build/deb-rust-sncf/link.html @@ -0,0 +1,97 @@ + + + + {{ "link_title"|tr(lang) }} – {{ "index_title"|tr(lang) }} + + + + + + + + + + + +
+
+
+
+
+

{{ "link_title"|tr(lang) }}

+

{{ "link_desc1"|tr(lang)|safe }}

+

{{ "link_desc2"|tr(lang)|safe }}

+
+
+ +
+
+
+ + +
+ +
+
+ +
+
+ +
+
+

{{ "link_desc3"|tr(lang) }}

+
+ +
+

{{ "link_note"|tr(lang) }}{{ config.prune_days }}{{ "link_note2"|tr(lang) }}

+
+
+
+
+ + + diff --git a/docker/build/deb-rust-sncf/main.rs b/docker/build/deb-rust-sncf/main.rs new file mode 100644 index 0000000..574e473 --- /dev/null +++ b/docker/build/deb-rust-sncf/main.rs @@ -0,0 +1,118 @@ +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate diesel; +#[macro_use] +extern crate diesel_migrations; + +use actix_files::Files; +use actix_web::client::Client; +use actix_web::{web, App, HttpRequest, FromRequest, HttpServer}; +use diesel::prelude::*; +use diesel::r2d2::{self, ConnectionManager}; +use url::Url; + +use crate::config::CONFIG; +use crate::config::PAYLOAD_LIMIT; +use crate::forward::*; + +mod account; +mod config; +mod database; +mod errors; +mod forward; +mod sniff; +mod templates; + +// default to postgres +#[cfg(feature = "default")] +type DbConn = PgConnection; +#[cfg(feature = "default")] +embed_migrations!("migrations/postgres"); + +#[cfg(feature = "postgres")] +type DbConn = PgConnection; +#[cfg(feature = "postgres")] +embed_migrations!("migrations/postgres"); + +#[cfg(feature = "sqlite")] +type DbConn = SqliteConnection; +#[cfg(feature = "sqlite")] +embed_migrations!("migrations/sqlite"); + +#[cfg(feature = "mysql")] +type DbConn = MysqlConnection; +#[cfg(feature = "mysql")] +embed_migrations!("migrations/mysql"); + +type DbPool = r2d2::Pool>; + +#[actix_rt::main] +async fn main() -> std::io::Result<()> { + /* std::env::set_var("RUST_LOG", "actix_web=debug"); + env_logger::init();*/ + + println!("ta ta tala ~ SNCF init"); + + println!("Checking configuration file..."); + CONFIG.check_version(); + + if CONFIG.database_path.len() == 0 { + println!("No database specified. Please enter a MySQL, PostgreSQL or SQLite connection string in config.toml."); + } + + debug(&format!("Opening database {}", CONFIG.database_path)); + + let manager = ConnectionManager::::new(&CONFIG.database_path); + let pool = r2d2::Pool::builder() + .build(manager) + .expect("ERROR: main: Failed to create the database pool."); + + let conn = pool.get().expect("ERROR: main: DB connection failed"); + + println!("Running migrations..."); + embedded_migrations::run(&*conn).expect("ERROR: main: Failed to run database migrations"); + + let forward_url = + Url::parse(&CONFIG.nextcloud_url).expect("Couldn't parse the forward url from config"); + + println!( + "Now listening at {}:{}", + CONFIG.listening_address, CONFIG.listening_port + ); + + + + // starting the http server + let HttpServer::new(move || { + App::new() + .data(pool.clone()) + .data(Client::new()) + .data(forward_url.clone()) + /*.route("/mimolette", web::get().to(login))*/ + /*.route("/login", web::post().to(forward))*/ + /*.wrap(middleware::Compress::default())*/ + .service(Files::new("/assets/", "./templates/assets/").index_file("index.html")) + .route("/", web::get().to(index)) + .route("/link", web::post().to(forward_register)) + .route("/admin/{token}", web::get().to(forward_login)) + .default_service(web::route().to(forward)) + .data(String::configure(|cfg| cfg.limit(PAYLOAD_LIMIT))) + .app_data(actix_web::web::Bytes::configure(|cfg| { + cfg.limit(PAYLOAD_LIMIT) + })) + }) + .bind((CONFIG.listening_address.as_str(), CONFIG.listening_port))? + .system_exit() + .run() + .await +} + +pub fn debug(text: &str) { + if CONFIG.debug_mode { + println!("{}", text); + } +} + diff --git a/docker/build/deb-rust-sncf/templates.rs b/docker/build/deb-rust-sncf/templates.rs new file mode 100644 index 0000000..6adfe26 --- /dev/null +++ b/docker/build/deb-rust-sncf/templates.rs @@ -0,0 +1,59 @@ +use actix_web::HttpRequest; +use askama::Template; +use crate::config::Config; +#[derive(Template)] +#[template(path = "index.html")] +pub struct TplIndex<'a> { + pub lang: &'a str, + pub csrf_token: &'a str, +} +#[derive(Template)] +#[template(path = "error.html")] +pub struct TplError<'a> { + pub lang: &'a str, + pub error_msg: &'a str, +} +#[derive(Template)] +#[template(path = "link.html")] +pub struct TplLink<'a> { + pub lang: &'a str, + pub admin_token: &'a str, + pub config: &'a Config, +} + + +pub fn get_lang(req: &HttpRequest) -> String { + // getting language from client header + // taking the two first characters of the Accept-Language header, + // in lowercase, then parsing it. + // if it fails, returns "en" + if let Some(l) = req.headers().get("Accept-Language") { + if let Ok(s) = l.to_str() { + return s.to_lowercase()[..2].to_string(); + } + } + String::from("en") +} +mod filters { + use crate::config::LOC; + pub fn tr(key: &str, lang: &str) -> askama::Result { + let translation = LOC.get(key).ok_or_else(|| { + eprintln!("tr filter: couldn't find the key {}", key); + askama::Error::from(std::fmt::Error) + })?; + Ok(String::from( + translation + .get(lang) + .unwrap_or(translation.get("en").ok_or_else(|| { + eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key); + askama::Error::from(std::fmt::Error) + })?) + .as_str() + .ok_or_else(|| { + eprintln!("tr filter: lang {} in key {} is not str", lang, key); + askama::Error::from(std::fmt::Error) + })?, + )) + } +} + diff --git a/docker/build/nextcloud-web/Dockerfile b/docker/build/nextcloud-web/Dockerfile new file mode 100644 index 0000000..9e620af --- /dev/null +++ b/docker/build/nextcloud-web/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:alpine + +COPY nginx.conf /etc/nginx/nginx.conf diff --git a/docker/build/nextcloud-web/backup/nginx.conf b/docker/build/nextcloud-web/backup/nginx.conf new file mode 100644 index 0000000..7a8fec1 --- /dev/null +++ b/docker/build/nextcloud-web/backup/nginx.conf @@ -0,0 +1,173 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + #gzip on; + + upstream php-handler { + server nextcloud:9000; + } + + server { + listen 80; + + # Add headers to serve security related headers + # Before enabling Strict-Transport-Security headers please read into this + # topic first. + add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + # Path to the root of your installation + root /var/www/html; + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # The following 2 rules are only needed for the user_webfinger app. + # Uncomment it if you're planning to use this app. + #rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; + + # The following rule is only needed for the Social app. + # Uncomment it if you're planning to use this app. + #rewrite ^/.well-known/webfinger /public.php?service=webfinger last; + + location = /.well-known/carddav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + + location = /.well-known/caldav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + + # set max upload size + client_max_body_size 10G; + fastcgi_buffers 64 4K; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + # Uncomment if your server is build with the ngx_pagespeed module + # This module is currently not supported. + #pagespeed off; + + location / { + rewrite ^ /index.php; + } + + location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ { + deny all; + } + location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) { + deny all; + } + + location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) { + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + # fastcgi_param HTTPS on; + + # Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + + # Enable pretty urls + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { + try_files $uri/ =404; + index index.php; + } + + # Adding the cache control header for js, css and map files + # Make sure it is BELOW the PHP block + location ~ \.(?:css|js|woff2?|svg|gif|map)$ { + try_files $uri /index.php$request_uri; + add_header Cache-Control "public, max-age=15778463"; + # Add headers to serve security related headers (It is intended to + # have those duplicated to the ones above) + # Before enabling Strict-Transport-Security headers please read into + # this topic first. + #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Optional: Don't log access to assets + access_log off; + } + + location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { + try_files $uri /index.php$request_uri; + # Optional: Don't log access to other assets + access_log off; + } + } +} diff --git a/docker/build/nextcloud-web/nginx.conf b/docker/build/nextcloud-web/nginx.conf new file mode 100644 index 0000000..6594ce9 --- /dev/null +++ b/docker/build/nextcloud-web/nginx.conf @@ -0,0 +1,173 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + #gzip on; + + upstream php-handler { + server nextcloud:9000; + } + + server { + listen 80; + + # Add headers to serve security related headers + # Before enabling Strict-Transport-Security headers please read into this + # topic first. + #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + # Path to the root of your installation + root /var/www/html; + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # The following 2 rules are only needed for the user_webfinger app. + # Uncomment it if you're planning to use this app. + #rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; + + # The following rule is only needed for the Social app. + # Uncomment it if you're planning to use this app. + #rewrite ^/.well-known/webfinger /public.php?service=webfinger last; + + location = /.well-known/carddav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + + location = /.well-known/caldav { + return 301 $scheme://$host:$server_port/remote.php/dav; + } + + # set max upload size + client_max_body_size 10G; + fastcgi_buffers 64 4K; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + # Uncomment if your server is build with the ngx_pagespeed module + # This module is currently not supported. + #pagespeed off; + + location / { + rewrite ^ /index.php; + } + + location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ { + deny all; + } + location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) { + deny all; + } + + location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) { + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + # fastcgi_param HTTPS on; + + # Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + + # Enable pretty urls + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { + try_files $uri/ =404; + index index.php; + } + + # Adding the cache control header for js, css and map files + # Make sure it is BELOW the PHP block + location ~ \.(?:css|js|woff2?|svg|gif|map)$ { + try_files $uri /index.php$request_uri; + add_header Cache-Control "public, max-age=15778463"; + # Add headers to serve security related headers (It is intended to + # have those duplicated to the ones above) + # Before enabling Strict-Transport-Security headers please read into + # this topic first. + #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; + # + # WARNING: Only add the preload option once you read about + # the consequences in https://hstspreload.org/. This option + # will add the domain to a hardcoded list that is shipped + # in all major browsers and getting removed from this list + # could take several months. + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Optional: Don't log access to assets + access_log off; + } + + location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { + try_files $uri /index.php$request_uri; + # Optional: Don't log access to other assets + access_log off; + } + } +} diff --git a/docker/build/nextcloud/Dockerfile b/docker/build/nextcloud/Dockerfile new file mode 100644 index 0000000..4abef7d --- /dev/null +++ b/docker/build/nextcloud/Dockerfile @@ -0,0 +1,127 @@ +# DO NOT EDIT: created by update.sh from Dockerfile-alpine.template +FROM php:7.4-fpm-alpine3.12 + +# entrypoint.sh and cron.sh dependencies +RUN set -ex; \ + \ + apk add --no-cache \ + rsync \ + ; \ + \ + rm /var/spool/cron/crontabs/root; \ + echo '*/5 * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data + +# install the PHP extensions we need +# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html +RUN set -ex; \ + \ + apk add --no-cache --virtual .build-deps \ + $PHPIZE_DEPS \ + autoconf \ + freetype-dev \ + icu-dev \ + libevent-dev \ + libjpeg-turbo-dev \ + libmcrypt-dev \ + libpng-dev \ + libmemcached-dev \ + libxml2-dev \ + libzip-dev \ + openldap-dev \ + pcre-dev \ + postgresql-dev \ + imagemagick-dev \ + libwebp-dev \ + gmp-dev \ + ; \ + \ + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \ + docker-php-ext-configure ldap; \ + docker-php-ext-install -j "$(nproc)" \ + bcmath \ + exif \ + gd \ + intl \ + ldap \ + opcache \ + pcntl \ + pdo_mysql \ + pdo_pgsql \ + zip \ + gmp \ + ; \ + \ +# pecl will claim success even if one install fails, so we need to perform each install separately + pecl install APCu-5.1.19; \ + pecl install memcached-3.1.5; \ + pecl install redis-5.3.2; \ + pecl install imagick-3.4.4; \ + \ + docker-php-ext-enable \ + apcu \ + memcached \ + redis \ + imagick \ + ; \ + \ + runDeps="$( \ + scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + )"; \ + apk add --virtual .nextcloud-phpext-rundeps $runDeps; \ + apk del .build-deps + +# set recommended PHP.ini settings +# see https://docs.nextcloud.com/server/12/admin_manual/configuration_server/server_tuning.html#enable-php-opcache +RUN { \ + echo 'opcache.enable=1'; \ + echo 'opcache.interned_strings_buffer=8'; \ + echo 'opcache.max_accelerated_files=10000'; \ + echo 'opcache.memory_consumption=128'; \ + echo 'opcache.save_comments=1'; \ + echo 'opcache.revalidate_freq=1'; \ + } > /usr/local/etc/php/conf.d/opcache-recommended.ini; \ + \ + echo 'apc.enable_cli=1' >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \ + \ + echo 'memory_limit=512M' > /usr/local/etc/php/conf.d/memory-limit.ini; \ + \ + mkdir /var/www/data; \ + chown -R www-data:root /var/www; \ + chmod -R g=u /var/www + +VOLUME /var/www/html + + +ENV NEXTCLOUD_VERSION 20.0.1 + +RUN set -ex; \ + apk add --no-cache --virtual .fetch-deps \ + bzip2 \ + gnupg \ + ; \ + \ + curl -fsSL -o nextcloud.tar.bz2 \ + "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2"; \ + curl -fsSL -o nextcloud.tar.bz2.asc \ + "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ +# gpg key from https://nextcloud.com/nextcloud.asc + gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys 28806A878AE423A28372792ED75899B9A724937A; \ + gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2; \ + tar -xjf nextcloud.tar.bz2 -C /usr/src/; \ + gpgconf --kill all; \ + rm nextcloud.tar.bz2.asc nextcloud.tar.bz2; \ + rm -rf "$GNUPGHOME" /usr/src/nextcloud/updater; \ + mkdir -p /usr/src/nextcloud/data; \ + mkdir -p /usr/src/nextcloud/custom_apps; \ + chmod +x /usr/src/nextcloud/occ; \ + apk del .fetch-deps + +COPY *.sh upgrade.exclude / +COPY config/* /usr/src/nextcloud/config/ + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["php-fpm"] diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml new file mode 100644 index 0000000..55464c0 --- /dev/null +++ b/docker/compose/docker-compose.yml @@ -0,0 +1,185 @@ +version: '2.3' + +services: + + traefik: + image: traefik:2.4 + container_name: traefik + restart: always + command: + - "--accesslog.filepath=/var/log/access.log" + - "--log.level=WARNING" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + - "--certificatesresolvers.myresolver.acme.httpchallenge=true" + - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web" + - "--certificatesresolvers.myresolver.acme.storage=/acme.json" + ports: + - 80:80 + - 443:443 + networks: + - proxy + volumes: +# - /opt/docker/overlays/traefik/var/log:/var/log/ + - /opt/docker/overlays/traefik/acme.json:/acme.json + - /var/run/docker.sock:/var/run/docker.sock:ro + + nextcloud-db: + image: mariadb + container_name: nextcloud-db + command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW + restart: always + volumes: + - /opt/docker/overlays/nextcloud-db/mysql:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=AAAAAAAAAAAAAAAAAAAAAAAMYSQLROOT + - MYSQL_PASSWORD=AAAAAAAAAAAAAAAAAAAAAAAAAAAAMYSQL + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + networks: + - nextcloud + - sncf-db + ports: + - "127.0.0.1:3306:3306" + + redis: + image: redis:alpine + container_name: nextcloud-redis + restart: always + command: redis-server --requirepass AAAAAAAAAAAAAAAAAAAAAAAAAAAAREDIS + networks: + - nextcloud + + + nextcloud: + image: nextcloud:e95023790cc36274053af7930831a9aecbf32efd + build: https://github.com/nextcloud/docker.git#e95023790cc36274053af7930831a9aecbf32efd:20.0/fpm-alpine + container_name: nextcloud + restart: always + volumes: +# - /opt/docker/overlays/nextcloud/var/log:/var/log + - /opt/docker/overlays/nextcloud/html:/var/www/html + - /opt/docker/overlays/nextcloud/data:/var/www/data + - /opt/docker/overlays/nextcloud/skeleton:/var/www/skeleton + environment: + - MYSQL_HOST=nextcloud-db + - REDIS_HOST=redis + - REDIS_HOST_PASSWORD=AAAAAAAAAAAAAAAAAAAAAAAAAAAAREDIS + - MYSQL_PASSWORD=AAAAAAAAAAAAAAAAAAAAAAAAAAAAMYSQL + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + depends_on: + - nextcloud-db + - redis + security_opt: + - no-new-privileges:true + networks: + - nextcloud + - nextcloud-web + + nextcloud-web: + build: ../build/nextcloud-web + container_name: nextcloud-web + restart: always + volumes: +# - /opt/docker/overlays/nextcloud-web/var/log:/var/log + - /opt/docker/overlays/nextcloud/html:/var/www/html:ro + - /opt/docker/overlays/nextcloud/data:/var/www/data:ro + - /opt/docker/overlays/nextcloud/skeleton:/var/www/skeleton:ro + labels: + - traefik.docker.network=proxy + - traefik.enable=true + - traefik.protocol=http + - traefik.port=80 + - traefik.http.routers.nextlcoud.tls=true + - traefik.http.routers.nextcloud.entrypoints=websecure + - traefik.http.routers.nextcloud.tls.certresolver=myresolver + - traefik.http.routers.nextcloud.rule=Host(`oleola.ddns.net`) + - traefik.http.middlewares.nextcloud.headers.customRequestHeaders.X-Forwarded-Proto=https + - traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect, next-auth + - traefik.http.middlewares.next-auth.basicauth.users=luca:$$2y$$05$$DnZh.HwnTfrmVhgrFdZ9bu86fyZ6/1HNRgaCTxOyrHGOFNFw5AYlm + - traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011 + - traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true + - traefik.http.middlewares.nextcloud.headers.stsPreload=true + - traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav + - traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/ + depends_on: + - nextcloud + security_opt: + - no-new-privileges:true + networks: + - nextcloud + - proxy + - nextcloud-web +# logging: +# driver: syslog + + cron: + image: nextcloud:stable-fpm-alpine + restart: always + container_name: nextcloud-cron + volumes: + - /opt/docker/overlays/nextcloud/html:/var/www/html + - /opt/docker/overlays/nextcloud/data:/var/www/data + - /opt/docker/overlays/nextcloud/skeleton:/var/www/skeleton + entrypoint: /cron.sh + depends_on: + - nextcloud-db + - redis + networks: + - nextcloud + - proxy + +# deb-rust-sncf: +# build: ../build/deb-rust-sncf +# container_name: deb-rust-sncf +# restart: always +# volumes: +## - /opt/docker/overlays/deb-rust-sncf/var/log:/var/log +# - /opt/docker/overlays/nextcloud/html:/var/www/html +# - /opt/docker/overlays/nextcloud/data:/var/www/data +# - /opt/docker/overlays/nextcloud/skeleton:/var/www/skeleton +# labels: +# - "traefik.docker.network=proxy" +# - "traefik.enable=true" +# - "traefik.protocol=http" +# - "traefik.port=8000" +# - "traefik.http.routers.deb-rust-sncf.entrypoints=websecure" +# - "traefik.http.routers.deb-rust-sncf.tls.certresolver=myresolver" +# - "traefik.http.routers.deb-rust-sncf.rule=Host(`example.org`)" +# - "traefik.http.services.deb-rust-sncf.loadbalancer.server.port=8000" +# - "traefik.http.routers.deb-rust-sncf.middlewares=sncf, sncf-auth" +# - "traefik.http.middlewares.sncf-auth.basicauth.users=luca:$$2y$$05$$DnZh.HwnTfrmVhgrFdZ9bu86fyZ6/1HNRgaCTxOyrHGOFNFw5AYlm" +# - "traefik.http.middlewares.sncf.headers.customRequestHeaders.X-Forwarded-Proto=https" +# - "traefik.http.middlewares.sncf.headers.stsSeconds=155520011" +# - "traefik.http.middlewares.sncf.headers.stsIncludeSubdomains=true" +# - "traefik.http.middlewares.sncf.headers.stsPreload=true" +# - "traefik.http.middlewares.sncf-ratelimit.ratelimit.average=200" +# environment: +# - RUST_BACKTRACE=full +# depends_on: +# - nextcloud +# networks: +# - sncf-db +# - sncf-nc +# - proxy + +networks: + proxy: + external: true + nextcloud: + external: false + driver: bridge + nextcloud-web: + external: false + driver: bridge + sncf-nc: + external: false + driver: bridge + sncf-db: + external: false + driver: bridge diff --git a/docker/compose/nextcloud.yml b/docker/compose/nextcloud.yml new file mode 100644 index 0000000..8cf7a7f --- /dev/null +++ b/docker/compose/nextcloud.yml @@ -0,0 +1,130 @@ +version: '2.3' + +services: + nextcloud-db: + image: mariadb + command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW + container_name: nextcloud-db + restart: always + volumes: + - /home/docker/nextcloud/container-db:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD= + - MYSQL_PASSWORD= + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + networks: + - nextcloud + + nextcloud: + image: nextcloud:stable-fpm-alpine + restart: on-failure:5 + container_name: nextcloud + volumes: + - /home/docker/nextcloud/app:/var/www/html + environment: + - MYSQL_HOST=nextcloud-db + - MYSQL_PASSWORD= + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - REDIS_HOST=redis + - REDIS_HOST_PASSWORD= + depends_on: + - nextcloud-db + - redis + security_opt: + - no-new-privileges:true + networks: + - nextcloud + + nextcloud-cron: + image: nextcloud:stable-fpm-alpine + restart: always + container_name: nextcloud-cron + volumes: + - /home/docker/nextcloud/app:/var/www/html + entrypoint: /cron.sh + depends_on: + - nextcloud-db + - redis + networks: + - nextcloud + + + nextcloud-web: + build: ./nextcloud-web + restart: on-failure:5 + container_name: nextcloud-web + volumes: + - /home/docker/nextcloud/app:/var/www/html + labels: + - traefik.docker.network=proxy + - traefik.enable=true + - traefik.protocol=http + - traefik.port=80 + - traefik.http.routers.nextcloud.tls=true + - traefik.http.routers.nextcloud.entrypoints=websecure + - traefik.http.routers.nextcloud.tls.certresolver=myresolver + - traefik.http.routers.nextcloud.rule=Host(`pellets.journalismarena.eu`) + - traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect + - traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011 + - traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true + - traefik.http.middlewares.nextcloud.headers.stsPreload=true + - traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav + - traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/ + depends_on: + - nextcloud + security_opt: + - no-new-privileges:true + mem_limit: 4096M + memswap_limit: 4096M + networks: + - proxy + - nextcloud + + redis: + image: redis:alpine + restart: always + container_name: nextcloud-redis + command: redis-server --requirepass ZpP2FwwNeMXW7Fd + networks: + - nextcloud + + + collabora: + image: collabora/code + container_name: collabora + restart: unless-stopped + mem_limit: 4096m + environment: + - domain=collabora-docs\\.digitalcourage\\.de + - username=admin-user + - password=6MktK8fu9Xx8iKrMu + - extra_params=--o:logging.level=warning --o:ssl.enable=false --o:ssl.termination=true + cap_add: + - MKNOD + networks: + - proxy + labels: + - traefik.docker.network=proxy + - traefik.enable=true + - traefik.protocol=http + - traefik.port=9080 + - traefik.http.routers.collabora.tls=true + - traefik.http.routers.collabora.entrypoints=websecure + - traefik.http.routers.collabora.tls.certresolver=myresolver + - traefik.http.routers.collabora.rule=Host(`collabora-docs.digitalcourage.de`) + - traefik.http.routers.collabora.middlewares=collabora + - traefik.http.middlewares.collabora.headers.customRequestHeaders.X-Forwarded-Proto=https + - traefik.http.middlewares.collabora.headers.referrerPolicy=no-referrer + - traefik.http.middlewares.collabora.headers.stsSeconds=31536000 + - traefik.http.middlewares.collabora.headers.forceSTSHeader=true + - traefik.http.middlewares.collabora.headers.stsPreload=true + - traefik.http.middlewares.collabora.headers.stsIncludeSubdomains=true + - traefik.http.middlewares.collabora.headers.browserXssFilter=true + +networks: + proxy: + external: true + nextcloud: + external: false diff --git a/docker/compose/not.es b/docker/compose/not.es new file mode 100644 index 0000000..e6c2ece --- /dev/null +++ b/docker/compose/not.es @@ -0,0 +1,2 @@ +changed labels in docker-compose.yml under nextcloud-web nextcloud to sncf, middleware added sncf, the last two labels regarding +redirects and social apps strg k diff --git a/docker/compose/traefik-migration/traefik-migration-tool b/docker/compose/traefik-migration/traefik-migration-tool new file mode 100755 index 0000000..c58c807 Binary files /dev/null and b/docker/compose/traefik-migration/traefik-migration-tool differ diff --git a/docker/compose/traefik-migration/traefik.toml b/docker/compose/traefik-migration/traefik.toml new file mode 100644 index 0000000..036d0eb --- /dev/null +++ b/docker/compose/traefik-migration/traefik.toml @@ -0,0 +1,30 @@ +logLevel = "WARN" +defaultEntryPoints = ["http", "https"] + +# Connection to docker host system (docker.sock) +[docker] +endpoint = "unix:///var/run/docker.sock" +domain = "digitalcourage.de" +watch = true +# This will hide all docker containers that don't have explicitly +# set label to "enable" +exposedbydefault = false + +# Force HTTPS +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + entryPoint = "https" + [entryPoints.https] + address = ":443" + [entryPoints.https.tls] +# Let's encrypt configuration +[acme] + email = "operating@digitalcourage.de" + storage="acme.json" + entryPoint="https" + OnHostRule = true + [acme.httpChallenge] + entryPoint = "http" + diff --git a/docker/overlays/collabora/loolwsd.xml b/docker/overlays/collabora/loolwsd.xml new file mode 100644 index 0000000..63505c0 --- /dev/null +++ b/docker/overlays/collabora/loolwsd.xml @@ -0,0 +1,179 @@ + + + + + + de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru + + + + + + + + + + 1 + + 4 + 5 + https://app.vereign.com + false + 3600 + + + 30 + 300 + false + 0 + 8000 + 0 + 0 + 100 + 100 + + 10000 + 60 + 300 + 3072 + 85 + + + + + 120 + 900 + + + loleaflet.html + + + true + warning + false + + + /var/log/loolwsd.log + never + timestamp + true + 10 days + 10 + true + false + + + false + 82589933 + + + + false + + + + + + + + false + + + + + + all + any + + + + 192\.168\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3} + 127\.0\.0\.1 + ::ffff:127\.0\.0\.1 + ::1 + 172\.17\.[0-9]{1,3}\.[0-9]{1,3} + ::ffff:172\.17\.[0-9]{1,3}\.[0-9]{1,3} + + + + + + + false + true + /etc/loolwsd/cert.pem + /etc/loolwsd/key.pem + /etc/loolwsd/ca-chain.cert.pem + + + 1000 + + + + + + + + + true + true + + + + + + + + + false + false + + + + + notebookbar + + + + + + cloud-forms\.digitalcourage\.de + 10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + 172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + 172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + 192\.168\.[0-9]{1,3}\.[0-9]{1,3} + 192\.168\.1\.1 + 0 + false + + 900 + + + + cloud-forms.digitalcourage.de + + + + + + + + + + + true + + + true + false + admin + Mt5q7XeepHWGZrsOS7GrWLWv2h6jKirF + + + + + + diff --git a/docker/overlays/nextcloud/html/AUTHORS b/docker/overlays/nextcloud/html/AUTHORS new file mode 100644 index 0000000..eb80517 --- /dev/null +++ b/docker/overlays/nextcloud/html/AUTHORS @@ -0,0 +1,422 @@ +Nextcloud is written by: + - AW-UC + - Aaron Wood + - Abijeet + - Achim Königs + - Adam Williamson + - Administrator "Administrator@WINDOWS-2012" + - Adrian Brzezinski + - Aldo "xoen" Giambelluca + - Alecks Gates + - Alejandro Varela + - Alex Weirig + - Alexander A. Klimov + - Alexander Bergolth + - Alexey Pyltsyn + - Allan Nordhøy + - Andreas Fischer + - Andreas Pflug + - Andrew Brown + - André Gaul + - Ardinis + - Ari Selseng + - Arne Hamann + - Artem Kochnev + - Artem Sidorenko + - Arthur Schiwon + - Axel Helmert + - Bart Visscher + - Bartek Przybylski + - Bastien Durel + - Bastien Ho + - Benjamin Diele + - Benjamin Liles + - Bernhard Ostertag + - Bernhard Posselt + - Bernhard Reiter + - Birk Borkason + - Bjoern Schiessle + - Björn Schießle + - Blaok + - Boris Rybalkin + - Borjan Tchakaloff + - Brad Rubenstein + - Brandon Kirsch + - Branko Kokanovic + - Brice Maron + - Byron Marohn + - Carla Schroder + - Carlos Cerrillo + - Carsten Wiedmann + - Christian <16852529+cviereck@users.noreply.github.com> + - Christian Berendt + - Christian Jürges + - Christian Kampka + - Christian Oliff + - Christoph Schaefer "christophł@wolkesicher.de" + - Christoph Seitz + - Christoph Wickert + - Christoph Wurst + - Christopher Bartz + - Christopher Schäpers + - Christopher T. Johnson + - Clark Tomlinson + - Clement Wong + - Cornelius Kölbel + - Cthulhux + - Damjan Georgievski + - Dan Callahan + - Daniel Calviño Sánchez + - Daniel Hansson + - Daniel Jagszent + - Daniel Kesselberg + - Daniel Rudolf + - Daniel Schneider + - Dariusz Olszewski + - David Prévot + - David Toledo + - Denis Mosolov + - Derek + - Dominik Schmidt + - Donquixote + - Elijah Martin-Merrill + - Eric Masseran + - Estelle Poulin + - Evgeny Golyshev + - Fabrizio Steiner + - Felix Epp + - Felix Heidecke + - Felix Moeller + - Felix Nieuwenhuizen + - Felix Nüsse + - Felix Rupp + - Filis Futsarov + - Florent + - Florin Peter + - Flávio Gomes da Silva Lisboa + - Frank Isemann + - Frank Karlitschek + - François Kubler + - Frederic Werner + - Frédéric Fortier + - Gary Kim + - Georg Ehrke + - GrayFix + - Greta Doci + - GretaD + - Guillaume COMPAGNON + - Hemanth Kumar Veeranki + - Hendrik Leppelsack + - Holger Hees + - Ilja Neumann + - Individual IT Services + - J0WI + - Jaakko Salo + - Jacob Neplokh + - Jakob Sack + - Jakub Onderka + - Jan C. Borchardt + - Jan-Christoph Borchardt + - Jan-Philipp Litza + - Janis Köhr + - Jared Boone + - Jarkko Lehtoranta + - Jean-Louis Dupond + - Jens-Christian Fischer + - Jesús Macias + - Joachim Bauch + - Joachim Sokolowski + - Joas Schilling + - Joel S + - Johan Björk + - Johannes Ernst + - Johannes Riedel + - Johannes Schlichenmaier + - Johannes Willnecker + - John Molakvoæ (skjnldsv) + - Jonas Sulzer + - Jonny007-MKD <1-23-4-5@web.de> + - Jos Poortvliet + - Jose Quinteiro + - Juan Pablo Villafañez + - Juan Pablo Villafáñez + - Julien Lutran + - Julien Veyssier + - Julius Haertl + - Julius Härtl + - Jörn Friedrich Dreyer + - KB7777 + - Kamil Domanski + - Kawohl + - Kenneth Newwood + - Kevin Lanni + - Kevin Ndung'u + - Kim Brose + - Klaas Freitag + - Knut Ahlers + - Ko- + - Konrad Bucheli + - Kristof Provost + - Kyle Fazzari + - Lars + - Lars Knickrehm + - Laurens Post + - Laurens Post + - Lauris Binde + - Lennart Rosam + - Lennart Rosam + - Leon Klingele + - Leon Klingele + - Liam Dennehy + - Liam JACK + - Lionel Elie Mamane + - Loki3000 + - Lorenzo M. Catucci + - Lukas Reschke + - Lukas Stabe + - Luke Policinski + - Magnus Walbeck + - Marcel Klehr + - Marcel Waldvogel + - Marin Treselj + - Mario Danic + - Mario Kolling + - Marius Blüm + - Marius David Wieschollek + - Markus Goetz + - Markus Staab + - MartB + - Martin + - Martin Konrad + - Martin Konrad + - Martin Mattel + - Marvin Thomas Rabe + - Masaki Kawabata Neto + - MasterOfDeath + - Matthew Setter + - Max Kovalenko + - Maxence Lange + - Maxence Lange + - Maxence Lange + - MichaIng <28480705+MichaIng@users.noreply.github.com> + - MichaIng + - Michael Gapczynski + - Michael Göhler + - Michael Jobst + - Michael Kuhn + - Michael Letzgus + - Michael Roitzsch + - Michael Roth + - Michael Weimann + - Michael Zamot + - Michał Węgrzynek + - Miguel Prokop + - Mikael Hammarin + - Mitar + - Mohammed Abdellatif + - Morris Jobke + - Nicolai Ehemann + - Nicolas Grekas + - Nils + - Nils Wittenbrink + - Nmz + - Noveen Sachdeva + - Ole Ostergaard + - Ole Ostergaard + - Oliver Gasser + - Oliver Kohl D.Sc. + - Oliver Salzburg + - Oliver Wegner + - Olivier Paroz + - Owen Winkler + - Pascal de Bruijn + - Patrick Paysant + - Patrik Kernstock + - Pauli Järvinen + - Pavel Krasikov + - Pellaeon Lin + - Peter Kubica + - Phil Davis + - Philipp Kapfer + - Philipp Schaffrath + - Philipp Staiger + - Philippe Jung + - Pierre Ozoux + - Pierre Rudloff + - Piotr Filiciak + - Piotr M + - Piotr Mrowczynski + - Piotr Mrówczyński + - Qingping Hou + - Ralph Krimmel + - Ramiro Aparicio + - Randolph Carter + - Rayn0r + - RealRancor + - RealRancor + - Rello + - Remco Brenninkmeijer + - Rinat Gumirov + - Robert Dailey + - Robin Appelman + - Robin McCorkell + - Robin Müller + - Roeland Jago Douma + - Roger Szabo + - Roland Tapken + - Romain Rivière + - Roman Kreisel + - Ross Nicoll + - Ruben Homs + - RussellAult + - Rémy Jacquin + - S. Cat <33800996+sparrowjack63@users.noreply.github.com> + - SA + - Sam Bull + - Sam Tuke + - Samuel CHEMLA + - Sander Ruitenbeek + - Sander Ruitenbeek + - Sandro Lutz + - Sascha Sambale + - Sascha Wiswedel + - Scott Dutton + - Scott Dutton + - Scott Shambarger + - Sean Comeau + - Sebastian Döll + - Sebastian Steinmetz <462714+steiny2k@users.noreply.github.com> + - Sebastian Steinmetz + - Sebastian Wessalowski + - Semih Serhat Karakaya + - Senorsen + - Serge Martin + - Sergej Nikolaev + - Sergej Pupykin + - Sergey Shliakhov + - Sergio Bertolin + - Sergio Bertolín + - Simon Könnecke + - Simon Spannagel + - Simounet + - Sjors van der Pluijm + - Stefan Rado + - Stefan Schneider + - Stefan Weiberg + - Stefan Weil + - Steffen Lindner + - Stephan Müller + - Stephan Peijnik + - Stephen Cuppett + - Steven Bühner + - Sujith H + - Sven Strickroth + - Sylvia van Os + - Tekhnee + - Temtaime + - Thibaut GRIDEL + - Thomas Citharel + - Thomas Ebert + - Thomas Müller + - Thomas Pulzer + - Thomas Tanghus + - Tiago Flores + - Tigran Mkrtchyan + - Tim Dettrick + - Tim Obert + - Tim Terhorst + - TimObert + - Timo Förster + - Tobia De Koninck + - Tobia De Koninck + - Tobias Kaminsky + - Tobias Perschon + - Tom Needham + - Tomasz Paluszkiewicz + - Tor Lillqvist + - Unknown + - Valdnet <47037905+Valdnet@users.noreply.github.com> + - Victor Dubiniuk + - Viktor Szépe + - Vincent Chan + - Vincent Petry + - Vinicius Cubas Brand + - Vitor Mattos + - Vlastimil Pecinka + - Volkan Gezer + - Volker + - William Pain + - Xheni Myrtaj + - Xuanwo + - adrien + - alexweirig + - b108@volgograd "b108@volgograd" + - bladewing + - bline + - blizzz + - brad2014 + - brumsel + - cetra3 + - cmeh + - comradekingu + - dartcafe + - davidgumberg + - davitol + - derkostka + - duritong + - eduardo + - enoch + - exner104 <59639860+exner104@users.noreply.github.com> + - fabian + - felixboehm + - fnuesse + - fnuesse + - helix84 + - hkjolhede + - ideaship + - j-ed + - j3l11234 <297259024@qq.com> + - jaltek + - jknockaert + - josh4trunks + - karakayasemi + - korelstar + - lui87kw + - macjohnny + - marco44 + - martin-rueegg + - martin.mattel@diemattels.at + - martink-p <47943787+martink-p@users.noreply.github.com> + - michaelletzgus + - michag86 + - mmccarn + - nhirokinet + - nishiki + - onehappycat + - oparoz + - phisch + - rakekniven + - rawtaz + - root "root@oc.(none)" + - root + - rubo77 + - sammo2828 + - scambra + - scolebrook + - shkdee + - simonspa <1677436+simonspa@users.noreply.github.com> + - sualko + - tbartenstein + - tbelau666 + - tux-rampage + - v1r0x + - voxsim "Simon Vocella" + - waleczny + - zulan + - Łukasz Buśko + +With help from many libraries and frameworks including: + Open Collaboration Services + SabreDAV + jQuery + … diff --git a/docker/overlays/nextcloud/html/console.php b/docker/overlays/nextcloud/html/console.php new file mode 100644 index 0000000..c44c020 --- /dev/null +++ b/docker/overlays/nextcloud/html/console.php @@ -0,0 +1,105 @@ + + * @author Christoph Wurst + * @author Estelle Poulin + * @author Joas Schilling + * @author Ko- + * @author Lukas Reschke + * @author Michael Weimann + * @author Morris Jobke + * @author Patrick Paysant + * @author RealRancor + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +require_once __DIR__ . '/lib/versioncheck.php'; + +use OC\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\ConsoleOutput; + +define('OC_CONSOLE', 1); + +function exceptionHandler($exception) { + echo "An unhandled exception has been thrown:" . PHP_EOL; + echo $exception; + exit(1); +} +try { + require_once __DIR__ . '/lib/base.php'; + + // set to run indefinitely if needed + if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { + @set_time_limit(0); + } + + if (!OC::$CLI) { + echo "This script can be run from the command line only" . PHP_EOL; + exit(1); + } + + set_exception_handler('exceptionHandler'); + + if (!function_exists('posix_getuid')) { + echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL; + exit(1); + } + $user = posix_getuid(); + $configUser = fileowner(OC::$configDir . 'config.php'); + if ($user !== $configUser) { + echo "Console has to be executed with the user that owns the file config/config.php" . PHP_EOL; + echo "Current user id: " . $user . PHP_EOL; + echo "Owner id of config.php: " . $configUser . PHP_EOL; + echo "Try adding 'sudo -u #" . $configUser . "' to the beginning of the command (without the single quotes)" . PHP_EOL; + echo "If running with 'docker exec' try adding the option '-u " . $configUser . "' to the docker command (without the single quotes)" . PHP_EOL; + exit(1); + } + + $oldWorkingDir = getcwd(); + if ($oldWorkingDir === false) { + echo "This script can be run from the Nextcloud root directory only." . PHP_EOL; + echo "Can't determine current working dir - the script will continue to work but be aware of the above fact." . PHP_EOL; + } elseif ($oldWorkingDir !== __DIR__ && !chdir(__DIR__)) { + echo "This script can be run from the Nextcloud root directory only." . PHP_EOL; + echo "Can't change to Nextcloud root directory." . PHP_EOL; + exit(1); + } + + if (!function_exists('pcntl_signal') && !in_array('--no-warnings', $argv)) { + echo "The process control (PCNTL) extensions are required in case you want to interrupt long running commands - see http://php.net/manual/en/book.pcntl.php" . PHP_EOL; + } + + $application = new Application( + \OC::$server->getConfig(), + \OC::$server->getEventDispatcher(), + \OC::$server->getRequest(), + \OC::$server->getLogger(), + \OC::$server->query(\OC\MemoryInfo::class) + ); + $application->loadCommands(new ArgvInput(), new ConsoleOutput()); + $application->run(); +} catch (Exception $ex) { + exceptionHandler($ex); +} catch (Error $ex) { + exceptionHandler($ex); +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/.gitignore b/docker/overlays/nextcloud/html/custom_apps/apporder/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/.scrutinizer.yml b/docker/overlays/nextcloud/html/custom_apps/apporder/.scrutinizer.yml new file mode 100644 index 0000000..27e2311 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/.scrutinizer.yml @@ -0,0 +1,19 @@ +filter: + excluded_paths: + - 'templates/*' + - 'css/*' + - 'tests/*' +imports: + - php + - javascript + +tools: + external_code_coverage: + timeout: 3600 + +checks: + php: + # this is not working properly with core exceptions + catch_class_exists: false + line_length: + max_length: '80' diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/.travis.yml b/docker/overlays/nextcloud/html/custom_apps/apporder/.travis.yml new file mode 100644 index 0000000..cfa1d87 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/.travis.yml @@ -0,0 +1,44 @@ +sudo: false +language: php +php: + - 7.2 +env: + global: + - DB=sqlite + matrix: + - CORE_BRANCH=stable19 REPO=nextcloud/server + +matrix: + fast_finish: true + +before_install: + - wget https://phar.phpunit.de/phpunit-5.7.phar + - chmod +x phpunit-5.7.phar + - mkdir bin + - mv phpunit-5.7.phar bin/phpunit + - phpunit --version + - cd ../ + - git clone https://github.com/$REPO.git --recursive --depth 1 -b $CORE_BRANCH mainrepo + - mv apporder mainrepo/apps/ + +before_script: + # fill owncloud with default configs and enable apporder + - cd mainrepo + - mkdir data + - ./occ maintenance:install --database-name oc_autotest --database-user oc_autotest --admin-user admin --admin-pass admin --database $DB --database-pass='' + - ./occ app:enable apporder + - ./occ app:check-code apporder + - php -S localhost:8080 & + - cd apps/apporder + - export PATH="$PWD/bin:$PATH" + + +script: + - make test + +after_failure: + - cat ../../data/owncloud.log + +after_success: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/php-unit.clover diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/.tx/config b/docker/overlays/nextcloud/html/custom_apps/apporder/.tx/config new file mode 100644 index 0000000..e999dab --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/.tx/config @@ -0,0 +1,9 @@ +[main] +host = https://www.transifex.com +lang_map = bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja + +[nextcloud.apporder] +file_filter = translationfiles//apporder.po +source_file = translationfiles/templates/apporder.pot +source_lang = en +type = PO diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/CHANGELOG.md b/docker/overlays/nextcloud/html/custom_apps/apporder/CHANGELOG.md new file mode 100644 index 0000000..c0429e5 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.12.0] - 2021-03-03 + +### Added + +- Support for Nextcloud 21 + +## [0.11.0] - 2020-09-03 + +### Added + +- Support for Nextcloud 20 + +## [0.10.0] - 2020-05-22 + +### Added + +- Support for Nextcloud 19 +- Added functionality to force an admin-defined order + +### Fixed + +- Fix for app sorting in dark mode + +## [0.9.0] - 2019-12-22 +### Added +- Support for Nextcloud 18 + +## [0.8.0] - 2019-08-20 +### Added +- Support for Nextcloud 17 + +## [0.7.1] - 2019-04-27 +### Fixed +- Scanning of outdated files + +## [0.7.0] - 2019-04-11 +### Changed +- Add support for Nextcloud 16 + +## [0.6.0] - 2018-11-24 + +## [0.5.0] - 2018-08-03 + +## [0.4.1] - 2017-12-05 +### Fixed +- Fix bug in IE11 + +## [0.4.0] - 2017-08-04 +### Changed +- Move order changing to the personal/admin settings +- Make it possible to hide apps per user/as admin +- Add support for new Nextcloud menu + +## [0.3.3] - 2016-10-12 + +## [0.3.2] - 2016-09-06 + +## [0.3.1] - 2016-09-05 + +## [0.3.0] - 2016-09-04 +### Changed +- Drop support for Nextcloud 9 / OwnCloud 9.0 + +### Added +- Set default landing page per user (See README.md for details) + +## [0.2.0] - 2016-09-04 + diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/LICENSE b/docker/overlays/nextcloud/html/custom_apps/apporder/LICENSE new file mode 100644 index 0000000..dbbe355 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/Makefile b/docker/overlays/nextcloud/html/custom_apps/apporder/Makefile new file mode 100644 index 0000000..1fc1c9e --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/Makefile @@ -0,0 +1,177 @@ +# This file is licensed under the Affero General Public License version 3 or +# later. See the COPYING file. +# @author Bernhard Posselt +# @copyright Bernhard Posselt 2016 + +# Generic Makefile for building and packaging an ownCloud app which uses npm and +# Composer. +# +# Dependencies: +# * make +# * which +# * curl: used if phpunit and composer are not installed to fetch them from the web +# * tar: for building the archive +# * npm: for building and testing everything JS +# +# If no composer.json is in the app root directory, the Composer step +# will be skipped. The same goes for the package.json which can be located in +# the app root or the js/ directory. +# +# The npm command by launches the npm build script: +# +# npm run build +# +# The npm test command launches the npm test script: +# +# npm run test +# +# The idea behind this is to be completely testing and build tool agnostic. All +# build tools and additional package managers should be installed locally in +# your project, since this won't pollute people's global namespace. +# +# The following npm scripts in your package.json install and update the bower +# and npm dependencies and use gulp as build system (notice how everything is +# run from the node_modules folder): +# +# "scripts": { +# "test": "node node_modules/gulp-cli/bin/gulp.js karma", +# "prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update", +# "build": "node node_modules/gulp-cli/bin/gulp.js" +# }, + +app_name=$(notdir $(CURDIR)) +build_tools_directory=$(CURDIR)/build/tools +source_build_directory=$(CURDIR)/build/artifacts/source +source_package_name=$(source_build_directory)/$(app_name) +appstore_build_directory=$(CURDIR)/build/artifacts/appstore +appstore_package_name=$(appstore_build_directory)/$(app_name) +npm=$(shell which npm 2> /dev/null) +composer=$(shell which composer 2> /dev/null) + +all: build + +# Fetches the PHP and JS dependencies and compiles the JS. If no composer.json +# is present, the composer step is skipped, if no package.json or js/package.json +# is present, the npm step is skipped +.PHONY: build +build: +ifneq (,$(wildcard $(CURDIR)/composer.json)) + make composer +endif +ifneq (,$(wildcard $(CURDIR)/package.json)) + make npm +endif +ifneq (,$(wildcard $(CURDIR)/js/package.json)) + make npm +endif + +# Installs and updates the composer dependencies. If composer is not installed +# a copy is fetched from the web +.PHONY: composer +composer: +ifeq (, $(composer)) + @echo "No composer command available, downloading a copy from the web" + mkdir -p $(build_tools_directory) + curl -sS https://getcomposer.org/installer | php + mv composer.phar $(build_tools_directory) + php $(build_tools_directory)/composer.phar install --prefer-dist + php $(build_tools_directory)/composer.phar update --prefer-dist +else + composer install --prefer-dist + composer update --prefer-dist +endif + +# Installs npm dependencies +.PHONY: npm +npm: +ifeq (,$(wildcard $(CURDIR)/package.json)) + cd js && $(npm) run build +else + npm run build +endif + +# Removes the appstore build +.PHONY: clean +clean: + rm -rf ./build + +# Same as clean but also removes dependencies installed by composer, bower and +# npm +.PHONY: distclean +distclean: clean + rm -rf vendor + rm -rf node_modules + rm -rf js/vendor + rm -rf js/node_modules + +# Builds the source and appstore package +.PHONY: dist +dist: + make source + make appstore + +# Builds the source package +.PHONY: source +source: + rm -rf $(source_build_directory) + mkdir -p $(source_build_directory) + tar cvzf $(source_package_name).tar.gz --exclude-vcs \ + --exclude="../$(app_name)/build" \ + --exclude="../$(app_name)/js/node_modules" \ + --exclude="../$(app_name)/node_modules" \ + --exclude="../$(app_name)/*.log" \ + --exclude="../$(app_name)/js/*.log" \ + ../$(app_name) \ + + +# Builds the source package for the app store, ignores php and js tests +.PHONY: appstore +appstore: + rm -rf $(appstore_build_directory) + mkdir -p $(appstore_build_directory) + tar cvzf $(appstore_package_name).tar.gz \ + --exclude="../$(app_name)/build" \ + --exclude="../$(app_name)/tests" \ + --exclude="../$(app_name)/Makefile" \ + --exclude="../$(app_name)/*.log" \ + --exclude="../$(app_name)/phpunit*xml" \ + --exclude="../$(app_name)/composer.*" \ + --exclude="../$(app_name)/js/node_modules" \ + --exclude="../$(app_name)/js/tests" \ + --exclude="../$(app_name)/js/test" \ + --exclude="../$(app_name)/js/*.log" \ + --exclude="../$(app_name)/js/package.json" \ + --exclude="../$(app_name)/js/bower.json" \ + --exclude="../$(app_name)/js/karma.*" \ + --exclude="../$(app_name)/js/protractor.*" \ + --exclude="../$(app_name)/package.json" \ + --exclude="../$(app_name)/bower.json" \ + --exclude="../$(app_name)/karma.*" \ + --exclude="../$(app_name)/protractor\.*" \ + --exclude="../$(app_name)/.*" \ + --exclude="../$(app_name)/js/.*" \ + --exclude-vcs \ + ../$(app_name) + + +# Command for running JS and PHP tests. Works for package.json files in the js/ +# and root directory. If phpunit is not installed systemwide, a copy is fetched +# from the internet +.PHONY: test +test: +ifneq (,$(wildcard $(CURDIR)/js/package.json)) + cd js && $(npm) run test +endif +ifneq (,$(wildcard $(CURDIR)/package.json)) + $(npm) run test +endif +ifeq (, $(shell which phpunit 2> /dev/null)) + @echo "No phpunit command available, downloading a copy from the web" + mkdir -p $(build_tools_directory) + curl -sSL https://phar.phpunit.de/phpunit.phar -o $(build_tools_directory)/phpunit.phar + php $(build_tools_directory)/phpunit.phar -c phpunit.xml + php $(build_tools_directory)/phpunit.phar -c phpunit.integration.xml +else + phpunit -c phpunit.xml --coverage-clover build/php-unit.clover + phpunit -c phpunit.integration.xml --coverage-clover build/php-unit.clover +endif diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/README.md b/docker/overlays/nextcloud/html/custom_apps/apporder/README.md new file mode 100644 index 0000000..2b40931 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/README.md @@ -0,0 +1,30 @@ +# AppOrder + +[![Build Status](https://travis-ci.org/juliushaertl/apporder.svg?branch=master)](https://travis-ci.org/juliushaertl/apporder) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/juliushaertl/apporder/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/juliushaertl/apporder/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/juliushaertl/apporder/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/juliushaertl/apporder/?branch=master) + +Enable sorting the app icons from the personal settings. The order will be +saved for each user individually. Administrators can define a custom default +order. + +## Set a default order for all new users + +Go to the Admin settings > Additional settings and drag the icons under App order. + +## Use first app as default app + +You can easily let Nextcloud redirect your user to the first app in their +personal order by changing the following parameter in your config/config.php: + + 'defaultapp' => 'apporder', + +Users will now get redirected to the first app of the default order or to the +first app of the user order. + +# Installation + +## From git + +1. Clone the app into your apps/ directory: `git clone https://github.com/juliushaertl/apporder.git` +2. Enable it diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/app.php b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/app.php new file mode 100644 index 0000000..688afc9 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/app.php @@ -0,0 +1,25 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +\OCP\Util::addStyle('apporder', 'apporder'); +\OCP\Util::addScript('apporder', 'apporder'); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/info.xml b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/info.xml new file mode 100644 index 0000000..1078616 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/info.xml @@ -0,0 +1,43 @@ + + + apporder + AppOrder + Sort apps in the menu with drag and drop + +Enable sorting the app icons from the personal settings. The order will be saved for each +user individually. Administrators can define a custom default order. + +## Set a default order for all new users + +Go to the Admin settings > Additional settings and drag the icons under App order. + +## Use first app as default app + +You can easily let Nextcloud redirect your user to the first app in their +personal order by changing the following parameter in your config/config.php: + + 'defaultapp' => 'apporder', + +Users will now get redirected to the first app of the default order or to the +first app of the user order. + + 0.12.0 + agpl + Julius Härtl + AppOrder + customization + https://github.com/juliushaertl/apporder/issues + https://github.com/juliushaertl/apporder.git + https://download.bitgrid.net/nextcloud/apporder/apporder.png + + + + + OCA\AppOrder\Settings\AdminSettings + OCA\AppOrder\Settings\Section + OCA\AppOrder\Settings\PersonalSettings + OCA\AppOrder\Settings\Section + + + diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/routes.php b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/routes.php new file mode 100644 index 0000000..5938119 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/appinfo/routes.php @@ -0,0 +1,34 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +return [ + 'routes' => [ + ['name' => 'app#index', 'url' => '/', 'verb' => 'GET'], + ['name' => 'settings#getOrder', 'url' => '/getOrder', 'verb' => 'GET'], + ['name' => 'settings#savePersonal', 'url' => '/savePersonal', 'verb' => 'POST'], + ['name' => 'settings#saveDefaultOrder', 'url' => '/saveDefaultOrder', 'verb' => 'POST'], + ['name' => 'settings#savePersonalHidden', 'url' => '/savePersonalHidden', 'verb' => 'POST'], + ['name' => 'settings#saveDefaultHidden', 'url' => '/saveDefaultHidden', 'verb' => 'POST'], + ['name' => 'settings#saveDefaultForce', 'url' => '/saveDefaultForce', 'verb' => 'POST'], + ] +]; diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/css/apporder.css b/docker/overlays/nextcloud/html/custom_apps/apporder/css/apporder.css new file mode 100644 index 0000000..cc8e6da --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/css/apporder.css @@ -0,0 +1,57 @@ +#appsorter { + margin-top: 10px; + width: 100%; + max-width: 320px; +} +#appsorter li { + margin-bottom: 2px; + display: flex; +} + +#appsorter img { + width: 20px; + height: 20px; + margin: 1px; + margin-right: 10px; + -webkit-filter: invert(1); + filter: invert(1); + filter: progid:DXImageTransform.Microsoft.BasicImage(invert='1'); + opacity: .7; +} +.dark #appsorter img { + -webkit-filter: invert(0); + filter: invert(0); +} +#appsorter .placeholder { + border: 1px dashed #aaaaaa; + visibility: visible; + width: 100%; + height: 20px; + margin-bottom: 2px; + border-radius: 3px; +} + +#appsorter li input { + margin: 0; + padding: 0; + width: 14px; + height: 14px; + margin-top: -5px; + margin-right: 10px; +} + +#appsorter li p { + padding: 1px; +} + +#appsorterforce { + margin-bottom: 2px; + display: flex; +} + +#appsorterforce input { + margin: -5px 5px 0px 5px; + padding: 0; + width: 14px; + height: 14px; +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/js/apporder.js b/docker/overlays/nextcloud/html/custom_apps/apporder/js/apporder.js new file mode 100644 index 0000000..018699d --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/js/apporder.js @@ -0,0 +1,125 @@ +$(function () { + + var app_menu = $('#appmenu'); + if (!app_menu.length) + return; + + app_menu.css('opacity', '0'); + + var mapMenu = function(parent, order, hidden) { + available_apps = {}; + parent.find('li').each(function () { + var id = $(this).find('a').attr('href'); + if(hidden.indexOf(id) > -1){ + $(this).remove(); + } + available_apps[id] = $(this); + }); + + //Remove hidden from order array + order = order.filter(function(e){ + return !(hidden.indexOf(e) > -1); + }) + $.each(order, function (order, value) { + parent.prepend(available_apps[value]); + }); + }; + + // restore existing order + $.get(OC.generateUrl('/apps/apporder/getOrder'),function(data){ + var order_json = data.order; + var hidden_json = data.hidden; + var order = []; + var hidden = []; + try { + order = JSON.parse(order_json).reverse(); + } catch (e) { + order = []; + } + try { + hidden = JSON.parse(hidden_json); + } catch (e) { + hidden = []; + } + + if (order.length === 0) { + app_menu.css('opacity', '1'); + return; + } + + mapMenu($('#appmenu'), order, hidden); + mapMenu($('#apps').find('ul'), order, hidden); + $(window).trigger('resize'); + app_menu.css('opacity', '1'); + + }); + + // Sorting inside settings + $("#appsorter").sortable({ + forcePlaceholderSize: true, + placeholder: 'placeholder', + stop: function (event, ui) { + var items = []; + var url; + var type = $('#appsorter').data('type'); + if(type === 'admin') { + url = OC.generateUrl('/apps/apporder/saveDefaultOrder'); + } else { + url = OC.generateUrl('/apps/apporder/savePersonal'); + } + $("#appsorter").children().each(function (i, el) { + var item = $(el).find('p').data("url"); + items.push(item) + }); + var json = JSON.stringify(items); + $.post(url, { + order: json + }, function (data) { + $(event.srcElement).effect("highlight", {}, 1000); + }); + } + }); + + $(".apporderhidden").change(function(){ + var hiddenList = []; + var url; + var type = $("#appsorter").data("type"); + + if(type === 'admin') { + url = OC.generateUrl('/apps/apporder/saveDefaultHidden'); + } else { + url = OC.generateUrl('/apps/apporder/savePersonalHidden'); + } + + $(".apporderhidden").each(function(i, el){ + if(!el.checked){ + hiddenList.push($(el).siblings('p').attr('data-url')) + } + }); + + var json = JSON.stringify(hiddenList); + $.post(url, { + hidden: json + }, function (data) { + //$(event.srcElement).effect("highlight", {}, 1000); + }); + }); + + $("#forcecheckbox").change(function(){ + var hiddenList = []; + var url; + var type = $("#forcecheckbox").data("type"); + + if(type === 'admin') { + url = OC.generateUrl('/apps/apporder/saveDefaultForce'); + var checked = $("#forcecheckbox").get(0).checked; + + var json = JSON.stringify(checked); + $.post(url, { + force: json + }, function (data) { + // Not really anything to do here ... + }); + } + }); +}); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.js new file mode 100644 index 0000000..7684115 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "ترتيب التطبيقات", + "App Order" : "ترتيب التطبيقات", + "Drag the app icons to change their order." : "قم بسحب أيقونة التطبيق لتغيير ترتيبتها." +}, +"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.json new file mode 100644 index 0000000..71956f4 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ar.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "ترتيب التطبيقات", + "App Order" : "ترتيب التطبيقات", + "Drag the app icons to change their order." : "قم بسحب أيقونة التطبيق لتغيير ترتيبتها." +},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ast.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ast.json new file mode 100644 index 0000000..987e2b9 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ast.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "AppOrder", + "App Order" : "Orde d'aplicaciones", + "Drag the app icons to change their order." : "Arrastra los iconos de l'aplicación pa camudar el so orde." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/bg.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/bg.js new file mode 100644 index 0000000..eb34e23 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/bg.js @@ -0,0 +1,9 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Подредба на приложения", + "Sort apps in the menu with drag and drop" : "Променя реда на приложенията", + "App Order" : "Подредба на приложенията", + "Drag the app icons to change their order." : "Влачете иконите на приложенията, за да промените подредбата им" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/cs.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/cs.json new file mode 100644 index 0000000..25c0fb5 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/cs.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Pořadí aplikací", + "AppOrder" : "Pořadí aplikací", + "Sort apps in the menu with drag and drop" : "Měňte pořadí aplikací v nabídce pomocí „táhni a pusť“", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Umožňuje seřazení ikon aplikací prostřednictvím osobních nastavení. Pořadí bude uloženo\npro každého uživatele zvlášť. Správci mohou určit výchozí pořadí ikon.\n\n## Nastavení výchozího pořadí ikon pro nové uživatele\n\nJděte do Nastavení > Správa > Další nastavení a v sekci Pořadí aplikací popřetahujte ikony do vámi zvoleného pořadí.\n\n## Použití první aplikace jako výchozí\n\nUživatele můžete snadno přesměrovat na první aplikaci v jejich pořadí tím, že změníte následující parametr v souboru config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUživatelé nyní budou automaticky přesměrováni na první aplikaci výchozího popř. jimi nastaveného pořadí.", + "App Order" : "Pořadí aplikací", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Nastavit výchozí pořadí pro všechny uživatele. Toto bude ignorováno, pokud si uživatel nastavil své vlastní pořadí a výchozí pořadí není vynucováno.", + "Drag the app icons to change their order." : "Pořadí aplikací změníte přetažením jejich ikon.", + "Force the default order for all users:" : "Vynutit výchozí pořadí pro všechny uživatele:", + "If enabled, users will not be able to set a custom order." : "Pokud je zapnuto, uživatelé si nebudou moci upravovat pořadí." +},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/da.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/da.json new file mode 100644 index 0000000..f63fc72 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/da.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Apporden", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Sorter apps i menuen med træk og slip", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Aktiver sortering af app ikoner i personlige indstillinger. Rækkefølgen bliver gemt individuelt for hver bruger. Administratorer kan definere en anden standard rækkefølge.\n\n## Indstil en standard rækkefølge for alle nye brugere\n\nGå til Administrator indstillinger > yderligere indstillinger og træk ikonerne til App rækkefølge.\n\n## Benyt første app som standard app\n\nDu kan let lade Nextcloud guide din bruger til den første app in deres\npersonlige rækkefølge ved at ændre den følgende parameter i din config/config.php:\n\n 'defaultapp' => 'apporder',\n\nBrugere bliver nu dirigeret til den første app i standard rækkefølgen eller den første app i brugerens rækkefølge.", + "App Order" : "Apporden", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Angiv forvalgt rækkefølge for alle brugere. Dette ignoreres, hvis brugeren selv angiver egen rækkefølge, og den forvalgte rækkefølge er ikke påtvunget. ", + "Drag the app icons to change their order." : "Flyt appikonerne for at ændre ordenen.", + "Force the default order for all users:" : "Gennemtving den forvalgte rækkefølge for alle brugere:", + "If enabled, users will not be able to set a custom order." : "Hvis slået til, så kan brugere ikke angive egne rækkefølger. " +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/de.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/de.json new file mode 100644 index 0000000..4b7fa8b --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/de.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "App-Reihenfolge", + "AppOrder" : "App-Reihenfolge", + "Sort apps in the menu with drag and drop" : "Apps im Menü per Drag und Drop sortieren", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ermöglicht es, die Reihenfolge der App-Icons in den persönlichen Einstellungen anzupassen. Die Reihenfolge wird für jeden \nBenutzer getrennt gespeichert. Administratoren können eine Standard-Sortierung vorgeben.\n\n##Standard-Sortierung für neue Benutzer einstellen\n\nUnter Administratoren-Einstellungen > Zusätzliche Einstellungen die Icons in die gewünschte Reihenfolge ziehen.\n\nErste App als Standard-App setzen\nUm die Benutzer einfach zur ersten App in ihrer\npersönlichen Reihenfolge umzuleiten, einfach in der config/config.php folgenden Parameter setzen:\n\n 'defaultapp' => 'apporder',\n\nBenutzer werden nun zur ersten App der Standard-Reihenfolge oder ihrer\npersönlichen Reihenfolge umgeleitet.", + "App Order" : "App-Reihenfolge", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Standardreihenfolge für alle Benutzer einstellen. Diese Einstellung wird ignoriert, sofern der Benutzer eine eigene Reihenfolge eingestellt hat und die Standardreihenfolge nicht erzwungen wird.", + "Drag the app icons to change their order." : "Verschiebe die App-Symbole, um ihre Reihenfolge zu verändern.", + "Force the default order for all users:" : "Standardreihenfolge für alle Benutzer erzwingen:", + "If enabled, users will not be able to set a custom order." : "Wenn aktiviert, können Benutzer keine eigene Reihenfolge einstellen." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.js new file mode 100644 index 0000000..2dca2dd --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Σειρά εφαρμογών", + "AppOrder" : "Αίτηση Εφαρμογής", + "Sort apps in the menu with drag and drop" : "Ταξινόμηση εφαρμογών στο μενού με μεταφορά και απόθεση", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ενεργοποιήστε την ταξινόμηση των εικονιδίων των εφαρμογών από τις προσωπικές ρυθμίσεις. θα αποθηκευτεί για κάθε\nχρήστη ξεχωριστά. Οι διαχειριστές μπορούν να καθορίσουν μια προσαρμοσμένη προεπιλεγμένη σειρά.\n\n## Καθορίστε προεπιλογή για όλους τους νέους χρήστες\nΣτις ρυθμίσεις διαχειριστή > επιπλέον ρυθμίσεις και σέρετε και αποθέστε τα εικονίδια εφαρμογών.\n\n## Χρήση της πρώτης εφαρμογής ως προεπιλογή\n\nΠρορείτε εύκολα να επιτρέψετε στο Nextcloud να ανακατευθύνει τους χρήστες στην πρώτη εφαρμογή από τις\nπροσωπικές ρυθμίσεις αλλάζοντας την παράμετρο στο αρχείο config/config.php:\n\n 'defaultapp' => 'apporder',\n\nΟι χρήστες θα ανακατευθυνθούν στην πρώτη εφαρμογή απο την προεπιλογή ή στην πρώτη εφαρμογή του χρήστη.", + "App Order" : "Σειρά εφαρμογών", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Ορίστε μια προεπιλεγμένη σειρά για όλους τους χρήστες. Αυτό θα αγνοηθεί εάν ο χρήστης έχει ρυθμίσει μια προσαρμοσμένη σειρά και η προεπιλεγμένη δεν θα εφαρμοστεί.", + "Drag the app icons to change their order." : " Σύρετε τα εικονίδια των εφαρμογών για να αλλάξετε τη σειρά τους.", + "Force the default order for all users:" : "Επιβολή προεπιλογής σε όλους τους χρήστες:", + "If enabled, users will not be able to set a custom order." : "Αν ενεργοποιηθεί, οι χρήστες δεν θα μπορούν να ορίσουν μια προσαρμοσμένη σειρά." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.json new file mode 100644 index 0000000..2e13867 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/el.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Σειρά εφαρμογών", + "AppOrder" : "Αίτηση Εφαρμογής", + "Sort apps in the menu with drag and drop" : "Ταξινόμηση εφαρμογών στο μενού με μεταφορά και απόθεση", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ενεργοποιήστε την ταξινόμηση των εικονιδίων των εφαρμογών από τις προσωπικές ρυθμίσεις. θα αποθηκευτεί για κάθε\nχρήστη ξεχωριστά. Οι διαχειριστές μπορούν να καθορίσουν μια προσαρμοσμένη προεπιλεγμένη σειρά.\n\n## Καθορίστε προεπιλογή για όλους τους νέους χρήστες\nΣτις ρυθμίσεις διαχειριστή > επιπλέον ρυθμίσεις και σέρετε και αποθέστε τα εικονίδια εφαρμογών.\n\n## Χρήση της πρώτης εφαρμογής ως προεπιλογή\n\nΠρορείτε εύκολα να επιτρέψετε στο Nextcloud να ανακατευθύνει τους χρήστες στην πρώτη εφαρμογή από τις\nπροσωπικές ρυθμίσεις αλλάζοντας την παράμετρο στο αρχείο config/config.php:\n\n 'defaultapp' => 'apporder',\n\nΟι χρήστες θα ανακατευθυνθούν στην πρώτη εφαρμογή απο την προεπιλογή ή στην πρώτη εφαρμογή του χρήστη.", + "App Order" : "Σειρά εφαρμογών", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Ορίστε μια προεπιλεγμένη σειρά για όλους τους χρήστες. Αυτό θα αγνοηθεί εάν ο χρήστης έχει ρυθμίσει μια προσαρμοσμένη σειρά και η προεπιλεγμένη δεν θα εφαρμοστεί.", + "Drag the app icons to change their order." : " Σύρετε τα εικονίδια των εφαρμογών για να αλλάξετε τη σειρά τους.", + "Force the default order for all users:" : "Επιβολή προεπιλογής σε όλους τους χρήστες:", + "If enabled, users will not be able to set a custom order." : "Αν ενεργοποιηθεί, οι χρήστες δεν θα μπορούν να ορίσουν μια προσαρμοσμένη σειρά." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/en_GB.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/en_GB.json new file mode 100644 index 0000000..c8f44ea --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/en_GB.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "App order", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Sort apps in the menu with drag and drop", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order.", + "App Order" : "App Order", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced.", + "Drag the app icons to change their order." : "Drag the app icons to change their order.", + "Force the default order for all users:" : "Force the default order for all users:", + "If enabled, users will not be able to set a custom order." : "If enabled, users will not be able to set a custom order." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.js new file mode 100644 index 0000000..0379ea6 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.js @@ -0,0 +1,11 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Aplikaĵa ordo", + "AppOrder" : "Aplikaĵa ordo", + "Sort apps in the menu with drag and drop" : "Ordigi menuajn aplikaĵojn per ŝovo kaj demeto", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ebligas ordigi la aplikaĵajn piktogramojn el la personaj agordoj. Sian propran ordon havas ĉiu uzanto.\nAdministranto povas difini propran defaŭltan ordon.\n\n## Agordi defaŭltan ordon por ĉiuj novaj uzantoj\n\nIru al la administranto-agordoj > Plia agordo, kaj ŝovu la piktogramojn trovantajn en „Ordo de la aplikaĵoj“.\n\n## Uzi la unuan aplikaĵon kiel defaŭltan aplikaĵon\n\nVi povas agordi vian Nextcloud, por ke ĝi alidirektu la uzanton al la unua aplikaĵo de la ordo: ŝanĝu la jenan agordon en la dosiero „config/config.php“:\n\n 'defaultapp' => 'apporder',\n\nUzantoj alidirektiĝos al la unua elektita aplikaĵo de la defaŭlta ordo aŭ al la unua aplikaĵo de sia propra ordo.", + "App Order" : "Ordo de la aplikaĵoj", + "Drag the app icons to change their order." : "Ŝovi la aplikaĵajn piktogramojn por ŝanĝi ilian ordon." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.json new file mode 100644 index 0000000..29c12fc --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eo.json @@ -0,0 +1,9 @@ +{ "translations": { + "App order" : "Aplikaĵa ordo", + "AppOrder" : "Aplikaĵa ordo", + "Sort apps in the menu with drag and drop" : "Ordigi menuajn aplikaĵojn per ŝovo kaj demeto", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ebligas ordigi la aplikaĵajn piktogramojn el la personaj agordoj. Sian propran ordon havas ĉiu uzanto.\nAdministranto povas difini propran defaŭltan ordon.\n\n## Agordi defaŭltan ordon por ĉiuj novaj uzantoj\n\nIru al la administranto-agordoj > Plia agordo, kaj ŝovu la piktogramojn trovantajn en „Ordo de la aplikaĵoj“.\n\n## Uzi la unuan aplikaĵon kiel defaŭltan aplikaĵon\n\nVi povas agordi vian Nextcloud, por ke ĝi alidirektu la uzanton al la unua aplikaĵo de la ordo: ŝanĝu la jenan agordon en la dosiero „config/config.php“:\n\n 'defaultapp' => 'apporder',\n\nUzantoj alidirektiĝos al la unua elektita aplikaĵo de la defaŭlta ordo aŭ al la unua aplikaĵo de sia propra ordo.", + "App Order" : "Ordo de la aplikaĵoj", + "Drag the app icons to change their order." : "Ŝovi la aplikaĵajn piktogramojn por ŝanĝi ilian ordon." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es.js new file mode 100644 index 0000000..830f4d9 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Orden de apps", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Ordenar apps en el menú mediante arrastrar y soltar", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Permite ordenar los iconos de las apps desde la configuración personal. El orden se salvará individualmente para\ncada usuario. Los administradores puede definir un orden por defecto personalizado.\n\n## Configurar un orden por defecto para todos los usuarios nuevos\n\nConfiguración > Configuración adicional y arrastrar los iconos debajo de App Order.\n\n## Usar la primera app como app por defecto\n\nPuedes dejar que Nextcloud redireccione tu usuario a la primera app en el\norden personal con mucha facilidad. Hay que cambiar el siguiente parámetro en tu config/config.php:\n\n'defaultapp' => 'apporder',\n\nLos usuarios serán redirigidos a partir de ahora la primera app del orden por defecto o a la\nprimera app del orden del usuario.", + "App Order" : "Orden de apps", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Establecer un orden predeterminado para todos los usuarios. Esto se ignorará si el usuario ha configurado un orden personalizado o si el orden predeterminado no está forzado.", + "Drag the app icons to change their order." : "Arrastra los iconos de las aplicaciones para cambiar el orden.", + "Force the default order for all users:" : "Forzar orden para todos los usuarios:", + "If enabled, users will not be able to set a custom order." : "Si se activa, los usuarios no podrán personalizar el orden de las apps." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_419.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CL.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CL.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CL.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CO.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CO.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_CO.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_DO.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_DO.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_DO.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_EC.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_HN.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_HN.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_HN.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_MX.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_MX.js new file mode 100644 index 0000000..b29d16f --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_MX.js @@ -0,0 +1,10 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "Sort apps in the menu with drag and drop" : "Ordena las aplicaciones en el menú arrastrándolas", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Habilita el ordenamiento de los íconos de aplicaciones desde las configuraciones personales. El orden se guardará para cada \nusuario individualmente. Los adminsitradores pueden definir un orden predeterminado. \n\n### Establece el orden predeterminado para todos los usuarios\n\nVe a las configuraciones de Admin > Configuraciones adicionales y arrastra los íconos en orden de App.\n\n## Usa la primera aplicación como la aplicación predeterminada\n\nPuedes permitirle fácilmente a Nexcloud redirigir a tu usuario a la primera aplicación en su ordenamiento personal cambiando el siguiente parámetro en tu archivo config/config.php:\n\n'defaultapp' => 'apporder',\n\nLos usuarios ahora serán redirigidos a la primera aplicación del ordenamiento predeterminado o a la primera aplicación del ordenamiento de ese usuario. ", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_NI.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_NI.js new file mode 100644 index 0000000..e69de29 diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_NI.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_NI.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_NI.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PA.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PA.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PA.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PE.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PE.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PE.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PY.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PY.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_PY.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.json new file mode 100644 index 0000000..3d59c1c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_SV.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_UY.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_UY.js new file mode 100644 index 0000000..60b2091 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/es_UY.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "Orden de la aplicación", + "App Order" : "Orden de la aplicación", + "Drag the app icons to change their order." : "Arrastra los íconos de la aplicación para cambiar su orden." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/et_EE.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/et_EE.js new file mode 100644 index 0000000..3f561a7 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/et_EE.js @@ -0,0 +1,8 @@ +OC.L10N.register( + "apporder", + { + "AppOrder" : "AppOrder", + "App Order" : "Rakenduste järjestus", + "Drag the app icons to change their order." : "Rakenduste järjestuse muutmiseks lohista need sobivasse kohta." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.js new file mode 100644 index 0000000..3bf92b3 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Aplikazioen ordena", + "AppOrder" : "App-en ordena", + "Sort apps in the menu with drag and drop" : "Ordenatu menuko aplikazioak arrastatu eta jareginez", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Gaitu aplikazioen ikonoen ordena norbere ezarpenetatik aldatzea. Erabiltzaile bakoitzak\nbere ordena eduki dezake. Kudeatzaileek lehenetsitako ordena definitu dezakete.\n\n## Ezarri lehenetsitako ordena erabiltzaile berri guztientzat\n\nJoan Kudeatzaile ezarpenetara > Ezarpen gehigarrietara eta arrastatu App-en ordenaren azpiko ikonoak.\n\n## Erabili lehen aplikazioa lehenetsitakoa bezala\n\nNextcloudek erabiltzaile bakoitza bere ordena pertsonaleko lehen aplikaziorantz\nberbideratu dezake modu errazean, ezarpenetan honako parametroa aldatuz config/config.php:\n\n 'defaultapp' => 'apporder',\n\nErabiltzaileak berbideratuak izango dira ordena lehenetsiko lehen aplikaziora edo\nerabiltzailearen ordenaren lehen aplikaziora.", + "App Order" : "App-en ordena", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Ezarri lehenetsitako orden bat erabiltzaile guztientzat. Hau ezikusi egingo da erabiltzaileak orden pertsonalizatu bat konfiguratuta badu, eta orden lehenetsia ez badago behartuta.", + "Drag the app icons to change their order." : "Arrastatu app-en ikonoak ordena aldatzeko.", + "Force the default order for all users:" : "Behartu lehenetsitako ordena erabiltzaile guztientzat:", + "If enabled, users will not be able to set a custom order." : "Gaituta badago, erabiltzaileek ezingo dute orden pertsonalizatu bat ezarri." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.json new file mode 100644 index 0000000..f28e266 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/eu.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Aplikazioen ordena", + "AppOrder" : "App-en ordena", + "Sort apps in the menu with drag and drop" : "Ordenatu menuko aplikazioak arrastatu eta jareginez", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Gaitu aplikazioen ikonoen ordena norbere ezarpenetatik aldatzea. Erabiltzaile bakoitzak\nbere ordena eduki dezake. Kudeatzaileek lehenetsitako ordena definitu dezakete.\n\n## Ezarri lehenetsitako ordena erabiltzaile berri guztientzat\n\nJoan Kudeatzaile ezarpenetara > Ezarpen gehigarrietara eta arrastatu App-en ordenaren azpiko ikonoak.\n\n## Erabili lehen aplikazioa lehenetsitakoa bezala\n\nNextcloudek erabiltzaile bakoitza bere ordena pertsonaleko lehen aplikaziorantz\nberbideratu dezake modu errazean, ezarpenetan honako parametroa aldatuz config/config.php:\n\n 'defaultapp' => 'apporder',\n\nErabiltzaileak berbideratuak izango dira ordena lehenetsiko lehen aplikaziora edo\nerabiltzailearen ordenaren lehen aplikaziora.", + "App Order" : "App-en ordena", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Ezarri lehenetsitako orden bat erabiltzaile guztientzat. Hau ezikusi egingo da erabiltzaileak orden pertsonalizatu bat konfiguratuta badu, eta orden lehenetsia ez badago behartuta.", + "Drag the app icons to change their order." : "Arrastatu app-en ikonoak ordena aldatzeko.", + "Force the default order for all users:" : "Behartu lehenetsitako ordena erabiltzaile guztientzat:", + "If enabled, users will not be able to set a custom order." : "Gaituta badago, erabiltzaileek ezingo dute orden pertsonalizatu bat ezarri." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fa.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fa.js new file mode 100644 index 0000000..1de5679 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fa.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "سفارش برنامه", + "AppOrder" : "سفارش برنامه", + "Sort apps in the menu with drag and drop" : "مرتب کردن برنامه ها در فهرست با کشیدن و رها کردن", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "مرتب کردن نمادهای برنامه از تنظیمات شخصی را فعال کنید. سفارش برای هر کاربر به صورت جداگانه ذخیره می شود. سرپرستان می توانند یک سفارش پیش فرض سفارشی را تعریف کنند. # # # یک سفارش پیش فرض برای همه کاربران جدید تنظیم کنید. \"برو به تنظیمات مدیر> تنظیمات اضافی و نمادها را به ترتیب برنامه بکشید.\" ## از اولین برنامه به عنوان برنامه پیش فرض استفاده کنید. به راحتی می توانید با تغییر پارامتر زیر در پیکربندی / config.php خود ، Nextcloud را به کاربر خود به اولین برنامه به ترتیب شخصی خود هدایت کنید: 'defaultapp' => 'apporder' ، - اکنون کاربران استفاده می شوند. برنامه اول سفارش پیش فرض یا برنامه اول سفارش کاربر.", + "App Order" : "سفارش برنامه", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "یک سفارش پیش فرض برای همه کاربران تنظیم کنید. اگر کاربر سفارش سفارشی تنظیم کرده باشد ، و سفارش پیش فرض اجباری نیست ، این مورد نادیده گرفته می شود.", + "Drag the app icons to change their order." : "نمادهای برنامه را بکشید تا ترتیب آنها تغییر کند.", + "Force the default order for all users:" : "سفارش پیش فرض را برای همه کاربران مجبور کنید:", + "If enabled, users will not be able to set a custom order." : "در صورت فعال بودن ، کاربران قادر به تنظیم سفارش سفارشی نخواهند بود." +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.js new file mode 100644 index 0000000..ad0c312 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Sovellusjärjestys", + "AppOrder" : "Sovellusjärjestys", + "Sort apps in the menu with drag and drop" : "Järjestä sovellukset valikkoon vetämällä ja pudottamalla", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ota käyttöön toiminto sovelluskuvakkeiden uudelleenjärjestämiseen. Järjestelmänvalvoja voi määrittää oletusjärjestyksen, \nmutta käyttäjät voivat vaihtaa sen itselleen halutessaan\n\n## Määritä oletusjärjestys kaikille uusille käyttäjille\n\nAvaa Asetukset > Ylläpito > Sovellusjärjestys ja vedä sovellukset haluamaasi järjestykseen.\n\n## Käytä ensimmäistä sovellusta oletussovelluksena\n\nAseta Nextcloud ohjaamaan käyttäjä ensimmäiseen sovellukseen henkilökohtaisessa sovellusjärjestyksessä\nvaihtamalla seuraava parametri tiedostossa config/config.php:\n\n 'defaultapp' => 'apporder',\n\nKäyttäjät ohjataan nyt sovellusten oletusjärjestyksen ensimmäisen sovelluksen sijasta käyttäjän määrittämän sovellusjärjestyksen\nensimmäiseen sovellukseen.", + "App Order" : "Sovellusjärjestys", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Määritä sovellusten oletusjärjestys kaikille käyttäjille. Käyttäjät voivat halutessaan määrittää sovellukset itselleen eri järjestykseen.", + "Drag the app icons to change their order." : "Vedä sovellusten kuvakkeita vaihtaaksesi niiden järjestystä.", + "Force the default order for all users:" : "Pakota oletusjärjestys kaikille käyttäjille:", + "If enabled, users will not be able to set a custom order." : "Jos käytössä, käyttäjät eivät voi asettaa omavalintaista järjestystä." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.json new file mode 100644 index 0000000..04595b8 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fi.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Sovellusjärjestys", + "AppOrder" : "Sovellusjärjestys", + "Sort apps in the menu with drag and drop" : "Järjestä sovellukset valikkoon vetämällä ja pudottamalla", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ota käyttöön toiminto sovelluskuvakkeiden uudelleenjärjestämiseen. Järjestelmänvalvoja voi määrittää oletusjärjestyksen, \nmutta käyttäjät voivat vaihtaa sen itselleen halutessaan\n\n## Määritä oletusjärjestys kaikille uusille käyttäjille\n\nAvaa Asetukset > Ylläpito > Sovellusjärjestys ja vedä sovellukset haluamaasi järjestykseen.\n\n## Käytä ensimmäistä sovellusta oletussovelluksena\n\nAseta Nextcloud ohjaamaan käyttäjä ensimmäiseen sovellukseen henkilökohtaisessa sovellusjärjestyksessä\nvaihtamalla seuraava parametri tiedostossa config/config.php:\n\n 'defaultapp' => 'apporder',\n\nKäyttäjät ohjataan nyt sovellusten oletusjärjestyksen ensimmäisen sovelluksen sijasta käyttäjän määrittämän sovellusjärjestyksen\nensimmäiseen sovellukseen.", + "App Order" : "Sovellusjärjestys", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Määritä sovellusten oletusjärjestys kaikille käyttäjille. Käyttäjät voivat halutessaan määrittää sovellukset itselleen eri järjestykseen.", + "Drag the app icons to change their order." : "Vedä sovellusten kuvakkeita vaihtaaksesi niiden järjestystä.", + "Force the default order for all users:" : "Pakota oletusjärjestys kaikille käyttäjille:", + "If enabled, users will not be able to set a custom order." : "Jos käytössä, käyttäjät eivät voi asettaa omavalintaista järjestystä." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fr.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fr.js new file mode 100644 index 0000000..623e396 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/fr.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Ordre des applications", + "AppOrder" : "Ordre des apps", + "Sort apps in the menu with drag and drop" : "Triez les applications dans le menu en les faisant glisser", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Autoriser à trier les icônes des applications dans les paramètres personnels. L'ordre sera sauvegardé pour chaque utilisateur individuellement. Les administrateurs peuvent définir un ordre par défaut.\n\n## Mettre en place un ordre par défaut pour tous les nouveaux utilisateurs\n\nAllez dans Paramètres d'administration > Paramètres supplémentaires et glisser les icônes dans l'ordre voulu.\n\n## Utiliser la première application comme application par défaut\n\nVous pouvez facilement laisser Nextcloud rediriger votre utilisateur vers la première application de leur ordre personnel en changeant le paramètre suivant dans le fichier config/config.php:\n\n 'defaultapp' => 'apporder',\n\nL'utilisateur sera désormais redirigé vers sa première application de l'ordre par défaut ou la première application de son ordre.", + "App Order" : "Ordre des applications", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Définir un ordre par défaut pour tous les utilisateurs. Il sera ignoré si l'utilisateur a défini un ordre personnalisé, et que l'ordre par défaut n'est pas forcé.", + "Drag the app icons to change their order." : "Faites glisser les icônes des applications pour changer leur ordre.", + "Force the default order for all users:" : "Forcer l'ordre par défaut pour tous les utilisateurs :", + "If enabled, users will not be able to set a custom order." : "Si activé, les utilisateurs ne pourront plus sélectionner un ordre personnalisé." +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/gl.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/gl.json new file mode 100644 index 0000000..f2d3308 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/gl.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Orde das aplicacións", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Ordenar aplicacións no menú arrastrando e soltando", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Activa a ordenación das iconas das aplicacións dende os axustes persoais. A orde\ngardaráse individualmente para cada usuario. Os administradores poden definir\nunha orde personalizada predeterminada\n\n## Estabelecer unha orde predeterminada para todos os usuarios novos\n\nIr aos axustes de administración > Axustes adicionais e arrastrar as iconas na\norde desexada.\n\n## Usar a primeira aplicación como aplicación predeterminada\n\nPode deixar que Nextcloud redireccione o seu usuario á primeira aplicación na orde\npersoal con moita facilidade, só ten que cambiar o seguinte parámetro no ficheiro\nconfig/config.php:\n\n 'defaultapp' => 'apporder',\n\nA partir de agora, os usuarios serán redirixidos cara á primeira aplicación na\norde predeterminada ou cara á primeira aplicación na orde do usuario.", + "App Order" : "Orde das aplicacións", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Estabelece unha orde predeterminado para todos os usuarios. Isto ignorarase se o usuario tiver estabelecida unha orden personalizada e a orde predeterminada non estiver forzada.", + "Drag the app icons to change their order." : "Arrastre as iconas das aplicacións para cambiar a orde.", + "Force the default order for all users:" : "Forzar a orde predeterminada para todos os usuarios:", + "If enabled, users will not be able to set a custom order." : "Se está activado, os usuarios non poderán configurar unha orde personalizada." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/he.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/he.js new file mode 100644 index 0000000..9db4dd7 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/he.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "סידור יישומונים", + "AppOrder" : "סידור יישומונים", + "Sort apps in the menu with drag and drop" : "סידור יישומונים בתפריט באמצעות גרירה", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "אפשר מיון של אייקוני האפליקציות מההגדרות האישיות. הסדר תישמר עבור כל משתמש בנפרד. מנהלי המערכת יכולים להגדיר סדר ברירת מחדל מותאם אישית. ## הגדר סדר ברירת מחדל בשביל כל המשתמשים החדשים. \nעבור אל Admin settings>Additional settings, וגרור את הסמלים תחת App order. ## השתמש באפליקציה הראשונה כאפליקציית ברירת מחדל.\nבקלות אתה יכול לאפשר ל- Nextcloud להפנות את המשתמש שלך לאפליקציה הראשונה בסדר האישי שלו על ידי שינוי הפרמטר הבא ב- config / config.php שלך: \ndefault 'defaultapp' => 'apporder', now.\nהמשתמשים יופנו עכשיו אל האפליקציה הראשונה של הסדר ברירת המחדל, או לאפליקציה הראשונה של הסדר של המשתמש.", + "App Order" : "סידור יישומונים", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "הגדרת סדר בררת המחדל לכל המשתמשים. ההגדרה הזאת לא תילקח בחשבון אם המשתמש הגדיר סדר משלו ולא נאכף סדר כבררת מחדל.", + "Drag the app icons to change their order." : "יש לגרור את סמלי היישומונים כדי לשנות את הסדר שלהם.", + "Force the default order for all users:" : "לאלץ את סדר בררת המחדל לכל המשתמשים:", + "If enabled, users will not be able to set a custom order." : "אם האפשרות פעילה, המשתמשים לא יוכלו להחליף את הסדר." +}, +"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hr.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hr.json new file mode 100644 index 0000000..0854993 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hr.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Redoslijed aplikacija", + "AppOrder" : "Redoslijed aplikacija", + "Sort apps in the menu with drag and drop" : "Sortirajte aplikacije u izborniku povlačenjem i ispuštanjem", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Omogućite sortiranje ikona aplikacija iz osobnih postavki. Redoslijed će biti spremljen za svaku\npojedinačno. Administratori mogu definirati zadani redoslijed.\n\n## Postavite zadani redoslijed za sve nove korisnike\n\nIdite na Postavke administratora> Dodatne postavke i povucite ikone pod redoslijedom aplikacije.\n\n## Koristite prvu aplikaciju kao zadanu aplikaciju\n\nMožete pustiti neka Nextcloud preusmjeri korisnika na prvu aplikaciju njihovog\nosobnog redoslijeda promjenom sljedećeg parametra u vašem config / config.php:\n\n'defaultapp' => 'apporder',\n\nKorisnici će biti preusmjereni na prvu aplikaciju zadanog redoslijeda ili na\nprvu aplikaciju korisnika.", + "App Order" : "Redoslijed aplikacija", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Postavite zadani poredak za sve korisnike. On će biti zanemaren ako je korisnik postavio prilagođeni poredak i zadani poredak nije nametnut.", + "Drag the app icons to change their order." : "Povucite ikone aplikacija za promjenu redoslijeda.", + "Force the default order for all users:" : "Nametnite zadani poredak za sve korisnike:", + "If enabled, users will not be able to set a custom order." : "Ako je omogućeno, korisnici neće moći postaviti prilagođeni poredak." +},"pluralForm" :"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.js new file mode 100644 index 0000000..16ed78b --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Alkalmazássorrend", + "AppOrder" : "Alkalmazássorrend", + "Sort apps in the menu with drag and drop" : "Rendezze az alkalmazásait fogd és vidd módszerrel", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Lehetővé teszi az alkalmazásikonok rendezését a személyes beállításokban.\nA sorrend felhasználóként külön lesz mentve.\nAz adminisztrátorok egyéni alapértelmezett sorrendet adhatnak meg.\n\n## Alapértelmezett sorrend megadása az összes felhasználónak\n\nUgorjon az Adminisztrációs beállítások > További beállításokhoz, és húzza az ikonokat az alkalmazássorrend alatt.\n\n## Az első alkalmazás alapértelmezettként használata\n\nKönnyen beállítható, hogy a Nextcloud átirányítsa a felhasználót az\nelső alkalmazásra a saját sorrendjében, a következő paraméter\nbeállításával a config/config.php fájlban:\n\n 'defaultapp' => 'apporder',\n\nA felhasználók most már átirányításra kerülnek az alapértelmezett\nsorrend első alkalmazására, vagy az egyéni sorrendjük első\nalkalmazására.", + "App Order" : "Alkalmazássorrend", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Alapértelmezett sorrend beállítása az összes felhasználónak. Ez figyelmen kívül lesz hagyva, ha a felhasználó egyéni sorrendet állít be, az alapértelmezett sorrend nincs kikényszerítve.", + "Drag the app icons to change their order." : "Húzza az alkalmazásikonokat a sorrendjük megváltoztatásához.", + "Force the default order for all users:" : "Az alapértelmezett sorrend kikényszerítése az összes felhasználónál:", + "If enabled, users will not be able to set a custom order." : "Ha engedélyezett, akkor a felhasználók nem állíthatnak be egyéni sorrendet." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.json new file mode 100644 index 0000000..a992f72 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/hu.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Alkalmazássorrend", + "AppOrder" : "Alkalmazássorrend", + "Sort apps in the menu with drag and drop" : "Rendezze az alkalmazásait fogd és vidd módszerrel", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Lehetővé teszi az alkalmazásikonok rendezését a személyes beállításokban.\nA sorrend felhasználóként külön lesz mentve.\nAz adminisztrátorok egyéni alapértelmezett sorrendet adhatnak meg.\n\n## Alapértelmezett sorrend megadása az összes felhasználónak\n\nUgorjon az Adminisztrációs beállítások > További beállításokhoz, és húzza az ikonokat az alkalmazássorrend alatt.\n\n## Az első alkalmazás alapértelmezettként használata\n\nKönnyen beállítható, hogy a Nextcloud átirányítsa a felhasználót az\nelső alkalmazásra a saját sorrendjében, a következő paraméter\nbeállításával a config/config.php fájlban:\n\n 'defaultapp' => 'apporder',\n\nA felhasználók most már átirányításra kerülnek az alapértelmezett\nsorrend első alkalmazására, vagy az egyéni sorrendjük első\nalkalmazására.", + "App Order" : "Alkalmazássorrend", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Alapértelmezett sorrend beállítása az összes felhasználónak. Ez figyelmen kívül lesz hagyva, ha a felhasználó egyéni sorrendet állít be, az alapértelmezett sorrend nincs kikényszerítve.", + "Drag the app icons to change their order." : "Húzza az alkalmazásikonokat a sorrendjük megváltoztatásához.", + "Force the default order for all users:" : "Az alapértelmezett sorrend kikényszerítése az összes felhasználónál:", + "If enabled, users will not be able to set a custom order." : "Ha engedélyezett, akkor a felhasználók nem állíthatnak be egyéni sorrendet." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/id.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/id.json new file mode 100644 index 0000000..a643f14 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/id.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Urutan Aplikasi", + "AppOrder" : "UrutanAplikasi", + "Sort apps in the menu with drag and drop" : "Mengurutkan aplikasi pada menu secara tarik lepas", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Mengaktifkan mengurutkan ikon aplikasi melalui setelan personal. Urutan akan tersimpan pada tiap pengguna.\nAdministrator dapat mendefinisikan pengaturan urutan bawaan.\n\n## Atur setelan bawaan bagi semua pengguna\n\nMelalui setelan **Administrasi** > **Urutan aplikasi**, atur urutan dengan cara tarik lepas ikon pada daftar.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order.", + "App Order" : "Urutan Aplikasi", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Atur setelan umum bagi semua pengguna. Ini akan diabaikan, jika pengguna telah menyetel urutan khusus, maka setelan umum tidak digunakan.", + "Drag the app icons to change their order." : "Tarik ikon aplikasi untuk mengubah urutan.", + "Force the default order for all users:" : "Semua pengguna wajib menggunakan urutan umum.", + "If enabled, users will not be able to set a custom order." : "Jika diaktifkan, pengguna tidak dapat menyetel urutan khusus." +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.js new file mode 100644 index 0000000..f55eb1a --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Ordine applicazioni", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Ordina le applicazioni nel menu con il trascinamento e rilascio", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Consente di ordinare le icone delle applicazioni dalle impostazioni personali. L'ordine sarà salvato per ogni\nsingolo utente. Gli amministratori possono definire un ordine predefinito personalizzato.\n\n## Imposta un ordine predefinito per i nuovi utenti\n\nVai nelle impostazioni di amministrazione > Impostazioni aggiuntive e trascina le icone sotto Ordine applicazione.\n\n## Utilizza la prima applicazione come applicazione predefinita\n\nPuoi semplicemente lasciare che Nextcloud rediriga i tuoi utenti alla prima applicazione nel loro ordine personale modificando il parametro seguente nel file config/config.php:\n\n 'defaultapp' => 'apporder',\n\nGli utenti saranno quindi rediretti alla prima applicazione dell'ordine predefinito o alla prima applicazione dell'ordine dell'utente.", + "App Order" : "Ordine applicazioni", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Imposta un ordine predefinito per tutti gli utenti. Sarà ignorato se l'utente ha configurato un ordine personalizzato e l'ordine predefinito non è forzato.", + "Drag the app icons to change their order." : "Trascina le icone delle applicazioni per cambiare il loro ordine.", + "Force the default order for all users:" : "Forza l'ordine predefinito per tutti gli utenti:", + "If enabled, users will not be able to set a custom order." : "Se abilitata, gli utenti non potranno impostare un ordine personalizzato." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.json new file mode 100644 index 0000000..69cba91 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/it.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Ordine applicazioni", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Ordina le applicazioni nel menu con il trascinamento e rilascio", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Consente di ordinare le icone delle applicazioni dalle impostazioni personali. L'ordine sarà salvato per ogni\nsingolo utente. Gli amministratori possono definire un ordine predefinito personalizzato.\n\n## Imposta un ordine predefinito per i nuovi utenti\n\nVai nelle impostazioni di amministrazione > Impostazioni aggiuntive e trascina le icone sotto Ordine applicazione.\n\n## Utilizza la prima applicazione come applicazione predefinita\n\nPuoi semplicemente lasciare che Nextcloud rediriga i tuoi utenti alla prima applicazione nel loro ordine personale modificando il parametro seguente nel file config/config.php:\n\n 'defaultapp' => 'apporder',\n\nGli utenti saranno quindi rediretti alla prima applicazione dell'ordine predefinito o alla prima applicazione dell'ordine dell'utente.", + "App Order" : "Ordine applicazioni", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Imposta un ordine predefinito per tutti gli utenti. Sarà ignorato se l'utente ha configurato un ordine personalizzato e l'ordine predefinito non è forzato.", + "Drag the app icons to change their order." : "Trascina le icone delle applicazioni per cambiare il loro ordine.", + "Force the default order for all users:" : "Forza l'ordine predefinito per tutti gli utenti:", + "If enabled, users will not be able to set a custom order." : "Se abilitata, gli utenti non potranno impostare un ordine personalizzato." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ka_GE.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ka_GE.json new file mode 100644 index 0000000..1c5913a --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ka_GE.json @@ -0,0 +1,6 @@ +{ "translations": { + "AppOrder" : "AppOrder", + "App Order" : "აპლიკაციების განლაგება", + "Drag the app icons to change their order." : "გადაიტანეთ აპლიკაციის პიქტოგრამები მათი გალაგების შესაცვლელად." +},"pluralForm" :"nplurals=2; plural=(n!=1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ko.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ko.json new file mode 100644 index 0000000..c54d0b5 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ko.json @@ -0,0 +1,9 @@ +{ "translations": { + "App order" : "앱 순서", + "AppOrder" : "앱 순서", + "Sort apps in the menu with drag and drop" : "드래그 앤 드롭으로 메뉴에 있는 앱 정렬", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "개인 설정에서 앱 아이콘을 정렬하는 것을 허용합니다. 개별 사용자별로 정렬 순서를\n저장합니다. 관리자는 기본 사용자 정의 순서를 지정할 수 있습니다.\n\n## 모든 새 사용자의 기본 순서 지정\n\n관리자 설정 > 추가 설정으로 이동하여 앱 순서 메뉴 아래에 있는 아이콘을 드래그하십시오.\n\n## 첫 앱을 기본 앱으로 사용\n\nconfig/config.php 파일의 다음 인자를 수정하여 Nextcloud에서 사용자를\n첫 앱으로 안내할 수 있습니다:\n\n 'defaultapp' => 'apporder',\n\n사용자는 자동으로 기본 순서의 첫 앱이나 사용자 정의 순서의 첫 앱으로\n안내됩니다.", + "App Order" : "앱 순서", + "Drag the app icons to change their order." : "앱 아이콘을 드래그해서 순서를 변경할 수 있습니다." +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lt_LT.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lt_LT.js new file mode 100644 index 0000000..17fb2c7 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lt_LT.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Programėlių tvarka", + "AppOrder" : "Programėlių išdėstymo tvarka", + "Sort apps in the menu with drag and drop" : "Velkant rikiuoti meniu esančias programėles", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Įjunkite galimybę rūšiuoti programos ikonas asmeniniuose nustatymuose. Kiekvienas naudotojas galės išsisaugoti individualią \nikonų išdėstymo tvarką. Administratorius gali nustatyti numatytąją tvarką.\n\n## Nustatyti numatytąją išdėstymo tvarką visiems naujiems naudotojams\n\nPasirinkite Administratoriaus nustatymai > Papildomi nustatymai ir tempdami išrikiuokite programų tvarką.\n\n## Naudokite pirmąją programą kaip numatytąją \n\nJūs galite lengvai leisti Nextcloud programai nukreipti Jūsų naudotoją į pirmąją programą, pakeisdami sekančius parametrus config/config.php faile:\n \n'defaultapp' => 'apporder',\n\nNaudotojas bus nukreiptas į pirmąją programą, pagal numatytą arba personalinę naudotojo tvarką.", + "App Order" : "Programėlių tvarka", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Nustatyti numatytąją tvarką visiems naudotojams. Jos bus nepaisoma, jei naudotojas yra nusistatęs pasirinktinę tvarką, o numatytoji tvarka nėra priverstinė.", + "Drag the app icons to change their order." : "Norėdami keisti programėlių tvarką, vilkite jų piktogramas.", + "Force the default order for all users:" : "Priverstinai taikyti numatytąją tvarką visiems naudotojams:", + "If enabled, users will not be able to set a custom order." : "Jei įjungta, naudotojai negalės nusistatyti pasirinktinės tvarkos." +}, +"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lv.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lv.js new file mode 100644 index 0000000..deaddfc --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/lv.js @@ -0,0 +1,11 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Lietotņu izkārtojums", + "AppOrder" : "Lietotņu izkārtojums", + "Sort apps in the menu with drag and drop" : "Sakārto lietotnes lietotņu logā pārvelkot un nometot", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Iespējot lietotņu ikonu sakārtošanu no personīgiem iestatījumiem. Šis izkārtojums tiks saglabāta katram\nlietotājam induviduāli. Administratori var definēt specifisku noklusējuma izkārtojumu.\n\n## Uzstādīt noklusējuma izkārtojumu priekš visiem jauniem lietotājiem\n\nAizej uz Administrēšanas iestatījumiem > Papildus iestatījumi un pārvelc ikonas zem Lietotnes izkārtojums.\n\n## Uzstādīt pirmo lietotni kā noklusējuma lietotni\n\nTu vari vienkārši ļaut Nextcloud novirzīt lietotāju uz pirmo lietotni viņu\npersonīgajā izkārtojumā pārmainot sekojošo parametru savā config/config.php:\n\n 'defaultapp' => 'apporder'\n\nLietotāji nu tiks novirzīti uz pirmo lietotni savā noklusējuma izkārtojumā vai uz\npirmo lietotni lietotāja izkārtojumā.", + "App Order" : "Lietotņu izkārtojums", + "Drag the app icons to change their order." : "Pārvelc lietotņu ikonas, lai mainītu to izkārtojumu." +}, +"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nb.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nb.js new file mode 100644 index 0000000..f31b4a5 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nb.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "App-rekkefølge", + "AppOrder" : "App-rekkefølge", + "Sort apps in the menu with drag and drop" : "Sorter apper i menyen med dra og slipp", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Aktiver sortering av app-ikon fra personlige innstillinger. Rekkefølge blir lagret for hver bruker. Administrator kan tilpasse standard rekkefølge.\n\n## Definer standard rekkefølge for alle nye brukere\n\nÅpne administrator innstillinger > Ekstra innstillinger og dra ikoner i ønsket rekkefølge under \"App-rekkefølge\".\n\n## Bruk første app som standard app\n\nDu kan la Nextcloud redirigere bruker til appen som er først ved å endre følgende innstilling i config/config.php:\n\n 'defaultapp' => 'apporder',\n\nBruker vil bli redirigert til den appen som er først i app-menyen.", + "App Order" : "App-rekkefølge", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Angi en standardrekkefølge for alle brukere. Dette vil bli ignorert hvis brukeren har satt opp en tilpasset ordre, og standardrekkefølgen ikke blir tvunget.", + "Drag the app icons to change their order." : "Dra og slipp app ikonene for å endre rekkefølgen.", + "Force the default order for all users:" : "Tving standardrekkefølgen for alle brukere:", + "If enabled, users will not be able to set a custom order." : "Hvis aktivert, vil ikke brukere kunne angi en tilpasset rekkefølge." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nl.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nl.json new file mode 100644 index 0000000..2740693 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nl.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "App Volgorde", + "AppOrder" : "AppVolgorde", + "Sort apps in the menu with drag and drop" : "Sorteert apps in het menu met slepen", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Maakt sorteren van de app pictogrammen mogelijk via de persoonlijke instellingen. De volgorde blijft voor elke afzonderlijke gebruikers bewaard. Beheerders kunnen zelf een standaardvolgorde instellen.\n\n## Instellen standaardvolgorde voor alle nieuwe gebruikers\n\nGa naar Beheer instellingen > Aanvullende instellingen en sleep de pictogrammen onder App volgorde.\n\n## Gebruik eerste app als standaard app\n\nJe kunt Nextcloud je gebruikers makkelijk doorsturen naar de eerste app in hun persoonlijke volgorde dooe de volgende parameter in je config/config.php:\n\n 'defaultapp' => 'apporder',\n\nGebruikers worden nu doorgeleid naar de eerste app van de standaardvolgorde of naar de eerste app in hun eigen volgorde.", + "App Order" : "App Volgorde", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Stel een standaard volgorde in voor alle gebruikers. Dit zal genegeerd worden indien de gebruiker een aangepaste volgorde heeft ingesteld en de standaard volgorde is niet geforceerd.", + "Drag the app icons to change their order." : "Sleep de app iconen om hun volgorde te wijzigen.", + "Force the default order for all users:" : "Forceer de standaard volgorde voor alle gebruikers:", + "If enabled, users will not be able to set a custom order." : "Als dit ingesteld is, kunnen gebruikers niet meer een aangepaste volgorde instellen." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nn_NO.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nn_NO.json new file mode 100644 index 0000000..0d34980 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/nn_NO.json @@ -0,0 +1,9 @@ +{ "translations": { + "App order" : "App-rekkefølgje", + "AppOrder" : "AppRekkjefølgje", + "Sort apps in the menu with drag and drop" : "Sorter appar i menyen med dra og slepp", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Slå på sortering av app-ikona frå dei personlege innstillingane. Rekkefølgja vil verta lagra\nfor kvar brukar individuelt. Administratorar kan laga ei standard-rekkefølgje.\n\n## Vel ei standard rekkefølgje for nye brukarar\n\nGå til Administrasjon > Fleire innstillingar og dra ikona under App-rekkefølgje.\n\n## Bruk fyrste app som standard-app\n\nDu kan enkelt la Nextcloud styra brukaren til den fyrste appen i deira personlege rekkefølgje ved å endra denne parameteren i config/config.php:\n\n 'defaultapp' => 'apporder',\n\nBrukarane vil no verta styrte til den fyrste appen i standardrekkefølgja eller den\nfyrste appen i brukaren si rekkefølgje.", + "App Order" : "App-rekkjefølgje", + "Drag the app icons to change their order." : "Dra applikasjons ikona for å endre rekkjefølgja" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pl.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pl.json new file mode 100644 index 0000000..f751fdb --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pl.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Kolejność aplikacji", + "AppOrder" : "Kolejność aplikacji", + "Sort apps in the menu with drag and drop" : "Ustaw kolejność wyświetlania aplikacji metodą przeciągnij i upuść", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Włącz sortowanie ikon aplikacji w ustawieniach osobistych. Kolejność ikon zostanie zachowana dla każdego\nużytkownika indywidualnie. Administratorzy mogą zdefiniować domyślną kolejność.\n\n## Ustaw domyślną kolejność dla wszystkich nowych użytkowników\n\nPrzejdź do Ustawienia > Administracja > Ustawienia dodatkowe i przeciągnij ikony aplikacji aby zmienić ich kolejność\n\n## Użyj pierwszej aplikacji jako aplikacji domyślnej\n\nMożesz pozwolić Nextcloud na przekierowanie użytkownika do pierwszej aplikacji w\nwybranej przez niego kolejności, zmieniając następujący parametr w config / config.php:\n\n 'defaultapp' => 'apporder',\n\nUżytkownicy zostaną teraz przekierowani do pierwszej aplikacji z kolejności domyślnej lub do pierwszej aplikacji wybranej przez użytkownika.", + "App Order" : "Kolejność aplikacji", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Ustaw domyślną kolejność aplikacji dla wszystkich użytkowników. Opcja będzie zignorowana jeśli użytkownik ustali własną kolejność, chyba że kolejność domyślna jest wymuszona.", + "Drag the app icons to change their order." : "Przeciągnij ikony aplikacji, aby zmienić ich kolejność.", + "Force the default order for all users:" : "Wymuś domyślną kolejność dla wszystkich użytkowników:", + "If enabled, users will not be able to set a custom order." : "Jeśli ta opcja jest włączona, użytkownicy nie będą mogli ustawić własnej kolejności." +},"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_BR.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_BR.json new file mode 100644 index 0000000..dbb2af5 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_BR.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Classificar aplicativos", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Classificar os aplicativos no menu com arrastar e soltar", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ative a classificação dos ícones de aplicativos nas configurações. Isso será salvo para cada\nusuário individualmente. Administradores podem definir uma ordem padrão personalizada.\n\n## Definir uma ordem padrão para todos os novos usuários\n\nVá em Configurações de Administrador > Configurações Adicionais e arraste os ícones em Ordem do aplicativo.\n\n## Use o primeiro aplicativo como aplicativo padrão\n\nVocê pode fazer o Nextcloud redirecionar o usuário para o primeiro aplicativo da sua\nordem pessoal, alterando o seguinte parâmetro no seu config/config.php:\n\n 'defaultapp' => 'apporder',\n\nOs usuários agora serão redirecionados para o primeiro aplicativo padrão ou\npara o primeiro aplicativo na ordem do usuário.", + "App Order" : "Pedido de Aplicativo", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Defina um pedido padrão para todos os usuários. Isso será ignorado se o usuário tiver configurado um pedido personalizado e o pedido padrão não forçado.", + "Drag the app icons to change their order." : "Arraste os ícones dos aplicativos para mudar a ordem.", + "Force the default order for all users:" : "Forçar o pedido padrão para todos os usuários:", + "If enabled, users will not be able to set a custom order." : "Se ativado, os usuários não poderão fazer um pedido personalizado." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_PT.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_PT.js new file mode 100644 index 0000000..03fd0cd --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/pt_PT.js @@ -0,0 +1,11 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Ordenação da aplicação", + "AppOrder" : "OrdenaçãoAplicação", + "Sort apps in the menu with drag and drop" : "Ordene as aplicações no menu com arrastar e largar", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Ative a ordenação dos ícones de aplicação nas definições pessoais. A ordenação será guardada individualmente\npara cada utilizador. Os administradores podem definir uma ordenação predefinida personalizada.\n\n## Defina uma ordenação predefinida para todos os novos utilizadores\n\nVás às Definições de Administrador > Definições adicionais e arraste os ícones por debaixo da ordenação da Aplicação.\n\n## Use a primeira aplicação como a aplicação predefinida \n\nPode facilmente permitir que o Nextcloud redirecione o seu utilizador para a \nprimeira aplicação na sua ordenação pessoal, alterando o seguinte \nparâmetro no seu\nconfig/config.php:\n\n 'defaultapp' => 'apporder',\n\nOs utilizadores serão agora redirecionados para a primeira aplicação da ordenação predefinida ou para \na primeira aplicação da ordenação do utilizador.", + "App Order" : "Ordenação de Aplicação", + "Drag the app icons to change their order." : "Arraste os ícones da aplicação para alterar a sua ordem." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ru.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ru.js new file mode 100644 index 0000000..8ee92e8 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/ru.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Порядок приложений", + "AppOrder" : "Порядок приложений", + "Sort apps in the menu with drag and drop" : "Сортируйте приложения с помощью перетаскивания", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Включите сортировку иконок приложений в личных настройках. Заказ будет сохранен для каждого пользователя отдельно. Администраторы могут определять пользовательский список по умолчанию. ## Установить список по умолчанию для всех новых пользователей. Перейти к Настройки администратора > Дополнительные параметры и перетащить значки в разделе «Список приложений». ## Использовать первое приложение в качестве приложения по умолчанию Вы можете легко позволить Nextcloud перенаправить пользователя на первое приложение в своем личном списке, изменив следующий параметр в вашем config/config.php: 'defaultapp' => 'apporder', Теперь пользователи будут перенаправлены на первое приложение по умолчанию или в первое приложение в пользовательском списке.", + "App Order" : "Порядок приложений", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Задаёт для всех пользователей порядок приложений по умолчанию. Не распространяется на пользователей, которые самостоятельно задали порядок приложений при отключённом параметре «принудительно использовать заданный порядок приложений».", + "Drag the app icons to change their order." : "Перемещайте значки приложений для изменения порядка их следования.", + "Force the default order for all users:" : "Принудительно использовать заданный порядок приложений для всех пользователей:", + "If enabled, users will not be able to set a custom order." : "При включении этого параметра пользователи не смогут задавать свой порядок приложений." +}, +"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sk.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sk.json new file mode 100644 index 0000000..8487e21 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sk.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Zoradenie aplikácií", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Poradie aplikácií v ponuke je možné meniť potiahnutím", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Umožňuje zoradenie ikon a aplikácií prostredníctvom osobných nastavení. Poradie sa uloží\npre každého používateľa zvlášť. Správcovia môžu určiť predvolené poradie ikon.\n\n## Nastavenie predvoleného poradia ikon pre nových používateľov\n\nChoďte do Nastavenia > Správa > Ďalšie nastavenia a v sekcii Poradie aplikácií popreťahujte ikony do vami zvoleného poradia.\n\n## Použitie prvej aplikácie ako predvolené\n\nPoužívateľa môžete jednoducho presmerovať na prvú aplikáciu v ich poradí tým, že zmeníte nasledujúci parameter v súbore config/config.php:\n\n'defaultapp' => 'apporder',\n\nPoužívatelia teraz budú automaticky presmerovaní na prvú aplikáciu predvoleného resp. im nastaveného poradia.", + "App Order" : "Zoradenie aplikácií", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Nastaviť predvolenú objednávku pre všetkých používateľov. Toto bude ignorované, ak má užívateľ nastavenú vlastnú objednávku a predvolená objednávka nie je vynútená.", + "Drag the app icons to change their order." : "Presuňte ikony aplikácií a zmeňte ich poradie.", + "Force the default order for all users:" : "Vynútiť predvolenú objednávku pre všetkých používateľov:", + "If enabled, users will not be able to set a custom order." : "Ak je povolená, používatelia nebudú môcť nastaviť vlastnú objednávku." +},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sl.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sl.json new file mode 100644 index 0000000..0e4a979 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sl.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Razvrščevalnik menija", + "AppOrder" : "Razvrščevalnik AppOrder", + "Sort apps in the menu with drag and drop" : "Enostavno razvrščanje in urejanje prikaza programov v menijski vrstici", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Program omogoča razvrščanje ikon v menijski vrstici, možnosti pa so zbrane med osebnimi nastavitvami. Vrstni red se shrani za vsakega uporabnika posebej,\nskrbniki pa lahko določajo poseben vrstni red.\n\n## Nastavitev privzetega vrstnegar eda za vse uporabnike\n\nMed skrbniškimi nastavitvami > Dodante nastavitve je na voljo seznam, ki ga je mogoče razvrstiti in posamezne predmete onemogočiti.\n\n## Uporabi prvi program kot privzeti\n\nPreusmerjanje uporabnikov na prvi program v razvrstitvi po meri je mogoče določiti s spreminjanjem parametrov v datoteki config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUporabniko bodo preusmerjeni na prvi program v privzeti razvistitvi\noziroma prvi program v uporabniški razvrstitvi.", + "App Order" : "Razvrščevalnik App Order", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Nastavi privzeto razvrstitev za vse uporabnike. Spremembe se pri uporabnikih ne izrazijo, če si menije sami preuredijo po meri oziroma če razvrstitev ni vsiljena.", + "Drag the app icons to change their order." : "S potegom predmeta je mogoče spreminjati vrstni red programov v menijski vrstici.", + "Force the default order for all users:" : "Vsili privzeti vrstni red za vse uporabnike:", + "If enabled, users will not be able to set a custom order." : "Izbrana možnost onemogoča uporabniku prilagajanje razvrstitve po meri." +},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sv.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sv.js new file mode 100644 index 0000000..ab2b81d --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/sv.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Appordning", + "AppOrder" : "AppOrder", + "Sort apps in the menu with drag and drop" : "Sortera appar i menyn genom att dra och släppa", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Aktivera sortering av appikoner från de personliga inställningarna. Ordern sparas för varje\nanvändaren individuellt. Administratörer kan definiera en anpassad standardordning.\n\n## Ange en standardorder för alla nya användare\n\nGå till administratörsinställningarna > ytterligare inställningar och dra ikonerna under apporder.\n\n## Använd första appen som standard app\n\nDu kan enkelt låta Nextcloud omdirigera din användare till den första appen i deras\npersonlig order genom att ändra följande parameter i din config/config.php:\n\n 'defaultapp' => 'apporder',\n\nAnvändare kommer nu att omdirigeras till den första appen i standardordern eller till\nförsta app av användarordern.", + "App Order" : "Appordning", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Välj en standardsortering för alla användare. Om användaren valt en annan sortering kommer standardvalet att ignoreras.", + "Drag the app icons to change their order." : "Dra appikonerna för att ändra deras ordning.", + "Force the default order for all users:" : "Bestäm standardsortering för alla användare:", + "If enabled, users will not be able to set a custom order." : "Om aktiverat, kommer användarna inte att kunna välja egen sortering." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.js new file mode 100644 index 0000000..7cae68a --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Uygulama sıralaması", + "AppOrder" : "UygulamaSıralaması", + "Sort apps in the menu with drag and drop" : "Menüdeki uygulamaların sıralamasını sürükleyip bırakarak değiştirir", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Uygulama simgelerinin kişisel tercihe göre sıralanabilmesini sağlar. Sıralama her kullanıcıya \nözel olarak kaydedilir. Yöneticiler özel bir sıralamayı varsayılan olarak atayabilir.\n\n## Tüm yeni kullanıcılar için varsayılan sıralamayı ayarlamak\n\nYönetici ayarları > Ek ayarlar bölümüne giderek Uygulama sıralaması altındaki simgeleri\nsürükleyip bırakın.\n\n## İlk uygulamayı varsayılan uygulama olarak kullanmak\n\nconfig/config.php dosyasında aşağıdaki ayarı yaparak kullanıcıların uygulama listesindeki ilk\nuygulamanın varsayılan olarak açılması sağlanabilir:\n\n 'defaultapp' => 'apporder',\n\nBöylece kullanıcılar için varsayılan sıralamadaki ilk uygulama yerine tercih ettikleri \nsıralamadaki ilk uygulama açılır.", + "App Order" : "Uygulama Sıralaması", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Tüm kullanıcılar için geçerli olacak varsayılan sıralamayı ayarlayın. Kullanıcı özel bir sıralama seçtiyse bu seçenek yok sayılır ve varsayılan sıralama uygulanmaz.", + "Drag the app icons to change their order." : "Simgeleri sürükleyerek uygulamaları sıralayabilirsiniz.", + "Force the default order for all users:" : "Tüm kullanıcılara uygulanacak varsayılan sıralama:", + "If enabled, users will not be able to set a custom order." : "Bu seçenek etkinleştirildiğinde, kullanıcılar özel bir sıralama uygulayamaz." +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.json new file mode 100644 index 0000000..a3c591d --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/tr.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Uygulama sıralaması", + "AppOrder" : "UygulamaSıralaması", + "Sort apps in the menu with drag and drop" : "Menüdeki uygulamaların sıralamasını sürükleyip bırakarak değiştirir", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Uygulama simgelerinin kişisel tercihe göre sıralanabilmesini sağlar. Sıralama her kullanıcıya \nözel olarak kaydedilir. Yöneticiler özel bir sıralamayı varsayılan olarak atayabilir.\n\n## Tüm yeni kullanıcılar için varsayılan sıralamayı ayarlamak\n\nYönetici ayarları > Ek ayarlar bölümüne giderek Uygulama sıralaması altındaki simgeleri\nsürükleyip bırakın.\n\n## İlk uygulamayı varsayılan uygulama olarak kullanmak\n\nconfig/config.php dosyasında aşağıdaki ayarı yaparak kullanıcıların uygulama listesindeki ilk\nuygulamanın varsayılan olarak açılması sağlanabilir:\n\n 'defaultapp' => 'apporder',\n\nBöylece kullanıcılar için varsayılan sıralamadaki ilk uygulama yerine tercih ettikleri \nsıralamadaki ilk uygulama açılır.", + "App Order" : "Uygulama Sıralaması", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Tüm kullanıcılar için geçerli olacak varsayılan sıralamayı ayarlayın. Kullanıcı özel bir sıralama seçtiyse bu seçenek yok sayılır ve varsayılan sıralama uygulanmaz.", + "Drag the app icons to change their order." : "Simgeleri sürükleyerek uygulamaları sıralayabilirsiniz.", + "Force the default order for all users:" : "Tüm kullanıcılara uygulanacak varsayılan sıralama:", + "If enabled, users will not be able to set a custom order." : "Bu seçenek etkinleştirildiğinde, kullanıcılar özel bir sıralama uygulayamaz." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/uk.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/uk.json new file mode 100644 index 0000000..27b8e44 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/uk.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "Порядок застосунків", + "AppOrder" : "Перелік застосунків", + "Sort apps in the menu with drag and drop" : "Впорядкування застосунків у меню за допомогою перетягування елементів", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Дозволяє сортувати значки застосунків у персональних налаштуваннях. Порядок збурігається окремо для кожного користувача. Адміністратор може налаштувати власний початковий порядок.\n\n## Налаштувати початковий порядок для всіх нових користувачів\n\nПерейдіть у Налаштування адміністратора (Admin settings) > Додаткові налаштування (Additional settings) і перетянгіть значки у бажаному порядку.\n\n## Використовувати перший застосунок як початковий\n\nВи можете дозволити Nextcloud перенаправляти користувачів на перший застосунок у їхніх налаштуваннях за допомогою насткпного параметре у вашому файоі конфігурації config/config.php:\n\n 'defaultapp' => 'apporder',\n\nТепер користувачі будуть перенаправлятися одразу після входу на перший застосунок у їхніх налаштуваннях.", + "App Order" : "Впорядкування застосунків", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Встановіть типовий порядок для усіх користувачів. Це не буде застосовуватися, якщо користувач визначив власний порядок розташування елементів, при цьому типовий порядок не увімкнено.", + "Drag the app icons to change their order." : "Потягніть за іконку застосунку для зміни його позиції в переліку.", + "Force the default order for all users:" : "Застосувати типовий порядок для всіх користувачів:", + "If enabled, users will not be able to set a custom order." : "Якщо увімкнено, користувачі не зможуть визначати власний порядок." +},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/vi.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/vi.js new file mode 100644 index 0000000..ccfc4e9 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/vi.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "Thứ tự ứng dụng", + "AppOrder" : "Thứ-tự.Ứng-dụng", + "Sort apps in the menu with drag and drop" : "Sắp xếp các ứng dụng trong bảng chọn bằng cách kéo và thả", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "Bật khả năng sắp xếp các biểu tượng ứng dụng từ các thiết lập cá nhân. Thứ tự sẽ được lưu lại mỗi người dùng riêng biệt.\nCác quản trị viên có thể định dạng một thiết lập thứ tự mặc định.\n\n##Đặt một thiết lập thứ tự mặc địnhcho tất cả các người dùng.\n\nĐi đến các Thiết lập Quản trị viên > các Thiết lập Bổ sung và kéo các biểu tượng dưới danh sách Thứ tự Ứng dụng.\n\n##Sử dụng ứng đụng đầu tiên như là ứng dụng mặc định\n\nBạn có thể dễ dàng khiến Nextcloud điều hướng tự động người dùng của bạn đến ứng dụng đầu tiên trong thứ tự cá nhân của họ bằng cách thay đổi tham số dưới đây trong tệp config/config.php:\n\n 'defaultapp' => 'apporder',\n\nNgười dùng bây giờ sẽ được tái điều hướng đến ứng dụng đầu tiên của thứ tự mặc định hoặc đến ứng dụng đầu tiên của thứ tự tùy chỉnh riêng của người dùng. ", + "App Order" : "Thứ tự ứng dụng", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "Đặt một thứ tự mặc định cho tất cả các người dùng. Điều này có thể bị bỏ qua, nếu một người dùng đã thiết lập một tùy chỉnh thứ tự riêng, và thứ tự mặc định không bị ép buộc thực thi. ", + "Drag the app icons to change their order." : "Kéo biểu tượng ứng dụng và thay đổi thứ tự của nó.", + "Force the default order for all users:" : "Ép buộc thực thi thứ tự mặc định cho tất cả các người dùng:", + "If enabled, users will not be able to set a custom order." : "Nếu được kích hoạt, người dùng sẽ không thể đặt một tùy chỉnh thứ tự riêng." +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_CN.json b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_CN.json new file mode 100644 index 0000000..c1a0d70 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_CN.json @@ -0,0 +1,12 @@ +{ "translations": { + "App order" : "应用顺序", + "AppOrder" : "应用顺序", + "Sort apps in the menu with drag and drop" : "在菜单上拖拽调整应用排序", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "在个人设置中启用应用图标排序。排列顺序会为每个用户独立保存。\n管理员可以定义全局默认的排序。\n\n## 为所有用户设置默认排序\n\n转到管理员设置>其他设置,然后拖动应用图标来排序。\n\n## 将第一个应用作为默认应用\n\n您可以轻松地让 Nextcloud 将您的用户重定向到他们的第一个应用程序\n通过更改 config / config.php 中的以下参数来实现:\n\n 'defaultapp' => 'apporder',\n\n用户现在将被重定向到默认排序下的第一个应用程序或\n用户排序的第一个应用程序。", + "App Order" : "应用顺序", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "为所有用户设置默认顺序。 如果用户已设置自定义顺序,并且不强制使用默认顺序,则将忽略此设置。", + "Drag the app icons to change their order." : "拖动应用图标以修改顺序", + "Force the default order for all users:" : "对所有用户强制使用默认顺序:", + "If enabled, users will not be able to set a custom order." : "如果启用,用户将无法设置自定义顺序。" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_HK.js b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_HK.js new file mode 100644 index 0000000..ede6e90 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/l10n/zh_HK.js @@ -0,0 +1,14 @@ +OC.L10N.register( + "apporder", + { + "App order" : "應用程式順序", + "AppOrder" : "應用程式順序", + "Sort apps in the menu with drag and drop" : "拖曳來調整選單中應用程式的順序", + "Enable sorting the app icons from the personal settings. The order will be saved for each\nuser individually. Administrators can define a custom default order.\n\n## Set a default order for all new users\n\nGo to the Admin settings > Additional settings and drag the icons under App order.\n\n## Use first app as default app\n\nYou can easily let Nextcloud redirect your user to the first app in their\npersonal order by changing the following parameter in your config/config.php:\n\n 'defaultapp' => 'apporder',\n\nUsers will now get redirected to the first app of the default order or to the\nfirst app of the user order." : "允許使用者在個人設定當中調整應用程式圖示的順序,每個使用者可以獨立設定自己偏好的順序,管理員可以也設定預設的順序。\n\n## 為所有新使用者設定預設的順序\n\n前往「設定」>「其他設定」,然後拖曳「應用程式順序」下方的圖示\n\n## 使用第一個應用程式為預設應用程式\n\n您可以設定 Nextcloud 自動在登入時將使用者重導向至他們設定的順序中最前面的應用程式,您需要修改 config/config.php 中的參數如下:\n\n 'defaultapp' => 'apporder',\n\n設定完成之後使用者就會自動被重導向至他們設定的順序中最前面的應用程式。", + "App Order" : "應用程式順序", + "Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced." : "對所有使用者設定預設順序。如果使用者有設定自訂順序則會忽略,不強制使用預設順序。", + "Drag the app icons to change their order." : "拖曳應用程式的圖示來更改順序", + "Force the default order for all users:" : "強制讓所有使用者使用預設順序:", + "If enabled, users will not be able to set a custom order." : "若開啟,使用者將無法設定自訂順序。" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/AppController.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/AppController.php new file mode 100644 index 0000000..3f02b0b --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/AppController.php @@ -0,0 +1,86 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Controller; + +use \OCP\AppFramework\Controller; +use OCP\AppFramework\Http\RedirectResponse; +use \OCP\IRequest; +use \OCA\AppOrder\Service\ConfigService; +use OCA\AppOrder\Util; +use OCP\IURLGenerator; + +class AppController extends Controller { + + private $userId; + private $urlGenerator; + private $util; + + public function __construct($appName, + IRequest $request, + IURLGenerator $urlGenerator, + Util $util, $userId) { + parent::__construct($appName, $request); + $this->userId = $userId; + $this->urlGenerator = $urlGenerator; + $this->util = $util; + } + + /** + * @NoCSRFRequired + * @NoAdminRequired + * @return RedirectResponse + */ + public function index() { + $order = json_decode($this->util->getAppOrder()); + $hidden = json_decode($this->util->getAppHidden()); + + $firstPage = null; + + if ($order !== null && sizeof($order) > 0) { + if($hidden !== null && sizeof($hidden) > 0){ + foreach($order as $app){ + if(!in_array($app,$hidden)){ + $firstPage = $app; + break; + } + } + } + else{ + $firstPage = $order[0]; + } + } + if($firstPage===null) { + + $appId = 'files'; + + if (getenv('front_controller_active') === 'true') { + $firstPage = $this->urlGenerator->getAbsoluteURL('/apps/' . $appId . '/'); + } else { + $firstPage = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/'); + } + } + return new RedirectResponse($firstPage); + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/SettingsController.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/SettingsController.php new file mode 100644 index 0000000..ff6c69e --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Controller/SettingsController.php @@ -0,0 +1,181 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Controller; + +use \OCP\AppFramework\Controller; +use \OCP\AppFramework\Http\TemplateResponse; +use \OCP\IRequest; +use \OCP\INavigationManager; +use \OCA\AppOrder\Service\ConfigService; +use \OCA\AppOrder\Util; + +class SettingsController extends Controller { + + private $userId; + private $appConfig; + private $navigationManager; + private $util; + + public function __construct($appName, IRequest $request, ConfigService $appConfig, INavigationManager $urlGenerator, Util $util, $userId) { + parent::__construct($appName, $request); + $this->userId = $userId; + $this->appConfig = $appConfig; + $this->navigationManager = $urlGenerator; + $this->util = $util; + } + + /** + * Admin: render admin page + * FIXME: Move to dedicated class + * + * @return TemplateResponse + */ + public function adminIndex() { + // Private API call + $navigation = $this->navigationManager->getAll(); + $order = json_decode($this->appConfig->getAppValue('order')); + if($order === null) $order = array(); + $nav = $this->util->matchOrder($navigation, $order); + $hidden = json_decode($this->appConfig->getAppValue('hidden')); + if($hidden === null) $hidden = array(); + $force = json_decode($this->appConfig->getAppValue('force')); + if($force === null) $force = false; + return new TemplateResponse( + $this->appName, + 'admin', + ["nav" => $nav, 'type' => 'admin', 'hidden' => $hidden, 'force' => $force], + 'blank' + ); + } + + public function personalIndex() { + // Private API call + $navigation = $this->navigationManager->getAll(); + $order = json_decode($this->appConfig->getUserValue('order', $this->userId)); + if($order === null){ + $order = json_decode($this->appConfig->getAppValue('order')); + if($order === null) $order = array(); + } + $nav = $this->util->matchOrder($navigation, $order); + $hidden = json_decode($this->appConfig->getUserValue('hidden',$this->userId)); + if($hidden === null){ + $hidden = json_decode($this->appConfig->getAppValue('hidden')); + if($hidden === null) $hidden = array(); + } + $force = json_decode($this->appConfig->getAppValue('force')); + if($force === null) $force = false; + return new TemplateResponse( + $this->appName, + 'admin', + ["nav" => $nav, 'type' => 'personal', 'hidden' => $hidden, 'force' => $force], + 'blank' + ); + } + + /** + * Admin: save default order + * + * @param $order + * @return array response + */ + public function saveDefaultOrder($order) { + if (!is_null($order)) { + $this->appConfig->setAppValue('order', $order); + } + return array('status' => 'success', 'order' => $order); + } + + /** + * Return order for current user + * + * @NoAdminRequired + * @return array response + */ + public function getOrder() { + $order = $this->util->getAppOrder(); + $hidden = $this->util->getAppHidden(); + return array('status' => 'success', 'order' => $order, 'hidden' => $hidden); + } + + /** + * Save order for current user + * + * @NoAdminRequired + * @param $order string + * @return array response + */ + public function savePersonal($order) { + $this->appConfig->setUserValue('order', $this->userId, $order); + $response = array( + 'status' => 'success', + 'data' => array('message' => 'User order saved successfully.'), + 'order' => $order + ); + return $response; + } + + /** + * Save hidden for current user + * + * @NoAdminRequired + * @param $hidden string + * @return array response + */ + public function savePersonalHidden($hidden) { + $this->appConfig->setUserValue('hidden', $this->userId, $hidden); + $response = array( + 'status' => 'success', + 'data' => array('message' => 'User hidden saved successfully.'), + 'hidden' => $hidden + ); + return $response; + } + + /** + * Admin: save default hidden + * + * @param $hidden + * @return array response + */ + public function saveDefaultHidden($hidden) { + if (!is_null($hidden)) { + $this->appConfig->setAppValue('hidden', $hidden); + } + return array('status' => 'success', 'hidden' => $hidden); + } + + /** + * Admin: save force value + * + * @param $force + * @return array response + */ + public function saveDefaultForce($force) { + if (!is_null($force)) { + $this->appConfig->setAppValue('force', $force); + } + return array('status' => 'success', 'force' => $force); + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Service/ConfigService.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Service/ConfigService.php new file mode 100644 index 0000000..599cd26 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Service/ConfigService.php @@ -0,0 +1,54 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Service; + +use \OCP\IConfig; + +class ConfigService { + + private $config; + private $appName; + + public function __construct(IConfig $config, $appName) { + $this->config = $config; + $this->appName = $appName; + } + + public function getAppValue($key) { + return $this->config->getAppValue($this->appName, $key); + } + + public function setAppValue($key, $value) { + $this->config->setAppValue($this->appName, $key, $value); + } + + public function getUserValue($key, $userId) { + return $this->config->getUserValue($userId, $this->appName, $key); + } + + public function setUserValue($key, $userId, $value) { + $this->config->setUserValue($userId, $this->appName, $key, $value); + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/AdminSettings.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/AdminSettings.php new file mode 100644 index 0000000..6e152ee --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/AdminSettings.php @@ -0,0 +1,72 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Settings; + + +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IConfig; +use OCP\Settings\ISettings; + +class AdminSettings implements ISettings { + + /** @var IConfig */ + private $config; + /** @var \OC_Defaults */ + private $defaults; + + public function __construct(IConfig $config, \OC_Defaults $defaults) { + $this->config = $config; + $this->defaults = $defaults; + } + + /** + * @return TemplateResponse returns the instance with all parameters set, ready to be rendered + * @since 9.1 + */ + public function getForm() { + $response = \OC::$server->query(\OCA\AppOrder\Controller\SettingsController::class)->adminIndex(); + return $response; + } + + /** + * @return string the section ID, e.g. 'sharing' + * @since 9.1 + */ + public function getSection() { + return 'apporder'; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + * @since 9.1 + */ + public function getPriority() { + return 90; + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/PersonalSettings.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/PersonalSettings.php new file mode 100644 index 0000000..e259508 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/PersonalSettings.php @@ -0,0 +1,87 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Settings; + + +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IConfig; +use OCP\Settings\ISettings; + +class PersonalSettings implements ISettings { + + /** @var IConfig */ + private $config; + /** @var \OC_Defaults */ + private $defaults; + /** @var indicates admin-forced order */ + private $force_admin_order; + + public function __construct(IConfig $config, $appName, \OC_Defaults $defaults) { + $this->config = $config; + $this->defaults = $defaults; + $this->force_admin_order = json_decode($this->config->getAppValue($appName, 'force')) ?? false; + } + + /** + * @return TemplateResponse returns the instance with all parameters set, ready to be rendered + * @since 9.1 + */ + public function getForm() { + if ($this->force_admin_order) { + $response = null; + } else { + $response = \OC::$server->query(\OCA\AppOrder\Controller\SettingsController::class)->personalIndex(); + } + return $response; + } + + /** + * @return string the section ID, e.g. 'sharing' + * @since 9.1 + */ + public function getSection() { + if ($this->force_admin_order) { + return null; + } else { + return 'apporder'; + } + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + * @since 9.1 + */ + public function getPriority() { + if ($this->force_admin_order) { + return null; + } else { + return 90; + } + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/Section.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/Section.php new file mode 100644 index 0000000..2f37585 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Settings/Section.php @@ -0,0 +1,90 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder\Settings; + + +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\Settings\IIconSection; + +class Section implements IIconSection { + + private $config; + private $defaults; + private $urlGenerator; + private $l10n; + + public function __construct(IConfig $config, \OC_Defaults $defaults, IURLGenerator $urlGenerator, IL10N $l10n) { + $this->config = $config; + $this->defaults = $defaults; + $this->urlGenerator = $urlGenerator; + $this->l10n = $l10n; + } + + /** + * returns the relative path to an 16*16 icon describing the section. + * e.g. '/core/img/places/files.svg' + * + * @returns string + * @since 12 + */ + public function getIcon() { + return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg'); + } + + /** + * returns the ID of the section. It is supposed to be a lower case string, + * e.g. 'ldap' + * + * @returns string + * @since 9.1 + */ + public function getID() { + return 'apporder'; + } + + /** + * returns the translated name as it should be displayed, e.g. 'LDAP / AD + * integration'. Use the L10N service to translate it. + * + * @return string + * @since 9.1 + */ + public function getName() { + return $this->l10n->t('App order'); + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the settings navigation. The sections are arranged in ascending order of + * the priority values. It is required to return a value between 0 and 99. + * + * E.g.: 70 + * @since 9.1 + */ + public function getPriority() { + return 90; + } +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Util.php b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Util.php new file mode 100644 index 0000000..5295083 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/lib/Util.php @@ -0,0 +1,83 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\AppOrder; + +use OCA\AppOrder\Service\ConfigService; + +class Util { + + private $userId; + private $appConfig; + + public function __construct(ConfigService $appConfig, $userId) { + $this->userId = $userId; + $this->appConfig = $appConfig; + } + + public function getAppOrder() { + $order_user = $this->appConfig->getUserValue('order', $this->userId); + $order_default = $this->appConfig->getAppValue('order'); + $forced_order = json_decode($this->appConfig->getAppValue('force')) ?? false; + if ($order_user !== null && $order_user !== "" && !($forced_order)) { + $order = $order_user; + } else { + $order = $order_default; + } + return $order; + } + + public function matchOrder($nav, $order) { + $nav_tmp = array(); + $result = array(); + foreach ($nav as $app) { + $nav_tmp[$app['href']] = $app; + } + if(is_array($order)) { + foreach ($order as $app) { + if (array_key_exists($app, $nav_tmp)) { + $result[$app] = $nav_tmp[$app]; + } + } + } + foreach ($nav as $app) { + if (!array_key_exists($app['href'], $result)) { + $result[$app['href']] = $app; + } + } + return $result; + } + + public function getAppHidden() { + $hidden_user = $this->appConfig->getUserValue('hidden', $this->userId); + $hidden_default = $this->appConfig->getAppValue('hidden'); + $forced_order = json_decode($this->appConfig->getAppValue('force')) ?? false; + if ($hidden_user !== null && $hidden_user !== "" && !($forced_order)) { + $hidden = $hidden_user; + } else { + $hidden = $hidden_default; + } + return $hidden; + } + +} diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.integration.xml b/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.integration.xml new file mode 100644 index 0000000..c899f23 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.integration.xml @@ -0,0 +1,7 @@ + + + + ./tests/integration + + + \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.xml b/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.xml new file mode 100644 index 0000000..e5fd49c --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/phpunit.xml @@ -0,0 +1,12 @@ + + + + ./tests/unit + + + + + ./ + + + diff --git a/docker/overlays/nextcloud/html/custom_apps/apporder/templates/admin.php b/docker/overlays/nextcloud/html/custom_apps/apporder/templates/admin.php new file mode 100644 index 0000000..49d0580 --- /dev/null +++ b/docker/overlays/nextcloud/html/custom_apps/apporder/templates/admin.php @@ -0,0 +1,21 @@ +
+

t('App Order')) ?>

+ +

t('Set a default order for all users. This will be ignored, if the user has setup a custom order, and the default order is not forced.')); ?>

+ +

t('Drag the app icons to change their order.')); ?>

+
    + +
  • + > + +

    + +

    +
  • + +
+ +

t('Force the default order for all users:')); ?> > (t('If enabled, users will not be able to set a custom order.')); ?>)

+ +
diff --git a/docker/overlays/nextcloud/html/index.html b/docker/overlays/nextcloud/html/index.html new file mode 100644 index 0000000..1ed4f8b --- /dev/null +++ b/docker/overlays/nextcloud/html/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docker/overlays/nextcloud/html/lib/autoloader.php b/docker/overlays/nextcloud/html/lib/autoloader.php new file mode 100644 index 0000000..f1bd613 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/autoloader.php @@ -0,0 +1,190 @@ + + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author Lukas Reschke + * @author Markus Goetz + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use \OCP\AutoloadNotAllowedException; +use OCP\ILogger; + +class Autoloader { + /** @var bool */ + private $useGlobalClassPath = true; + /** @var array */ + private $validRoots = []; + + /** + * Optional low-latency memory cache for class to path mapping. + * + * @var \OC\Memcache\Cache + */ + protected $memoryCache; + + /** + * Autoloader constructor. + * + * @param string[] $validRoots + */ + public function __construct(array $validRoots) { + foreach ($validRoots as $root) { + $this->validRoots[$root] = true; + } + } + + /** + * Add a path to the list of valid php roots for auto loading + * + * @param string $root + */ + public function addValidRoot(string $root) { + $root = stream_resolve_include_path($root); + $this->validRoots[$root] = true; + } + + /** + * disable the usage of the global classpath \OC::$CLASSPATH + */ + public function disableGlobalClassPath() { + $this->useGlobalClassPath = false; + } + + /** + * enable the usage of the global classpath \OC::$CLASSPATH + */ + public function enableGlobalClassPath() { + $this->useGlobalClassPath = true; + } + + /** + * get the possible paths for a class + * + * @param string $class + * @return array an array of possible paths + */ + public function findClass(string $class): array { + $class = trim($class, '\\'); + + $paths = []; + if ($this->useGlobalClassPath && array_key_exists($class, \OC::$CLASSPATH)) { + $paths[] = \OC::$CLASSPATH[$class]; + /** + * @TODO: Remove this when necessary + * Remove "apps/" from inclusion path for smooth migration to multi app dir + */ + if (strpos(\OC::$CLASSPATH[$class], 'apps/') === 0) { + \OCP\Util::writeLog('core', 'include path for class "' . $class . '" starts with "apps/"', ILogger::DEBUG); + $paths[] = str_replace('apps/', '', \OC::$CLASSPATH[$class]); + } + } elseif (strpos($class, 'OC_') === 0) { + $paths[] = \OC::$SERVERROOT . '/lib/private/legacy/' . strtolower(str_replace('_', '/', substr($class, 3)) . '.php'); + } elseif (strpos($class, 'OCA\\') === 0) { + list(, $app, $rest) = explode('\\', $class, 3); + $app = strtolower($app); + $appPath = \OC_App::getAppPath($app); + if ($appPath && stream_resolve_include_path($appPath)) { + $paths[] = $appPath . '/' . strtolower(str_replace('\\', '/', $rest) . '.php'); + // If not found in the root of the app directory, insert '/lib' after app id and try again. + $paths[] = $appPath . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php'); + } + } elseif ($class === 'Test\\TestCase') { + // This File is considered public API, so we make sure that the class + // can still be loaded, although the PSR-4 paths have not been loaded. + $paths[] = \OC::$SERVERROOT . '/tests/lib/TestCase.php'; + } + return $paths; + } + + /** + * @param string $fullPath + * @return bool + * @throws AutoloadNotAllowedException + */ + protected function isValidPath(string $fullPath): bool { + foreach ($this->validRoots as $root => $true) { + if (substr($fullPath, 0, strlen($root) + 1) === $root . '/') { + return true; + } + } + throw new AutoloadNotAllowedException($fullPath); + } + + /** + * Load the specified class + * + * @param string $class + * @return bool + * @throws AutoloadNotAllowedException + */ + public function load(string $class): bool { + $pathsToRequire = null; + if ($this->memoryCache) { + $pathsToRequire = $this->memoryCache->get($class); + } + + if (class_exists($class, false)) { + return false; + } + + if (!is_array($pathsToRequire)) { + // No cache or cache miss + $pathsToRequire = []; + foreach ($this->findClass($class) as $path) { + $fullPath = stream_resolve_include_path($path); + if ($fullPath && $this->isValidPath($fullPath)) { + $pathsToRequire[] = $fullPath; + } + } + + if ($this->memoryCache) { + $this->memoryCache->set($class, $pathsToRequire, 60); // cache 60 sec + } + } + + foreach ($pathsToRequire as $fullPath) { + require_once $fullPath; + } + + return false; + } + + /** + * Sets the optional low-latency cache for class to path mapping. + * + * @param \OC\Memcache\Cache $memoryCache Instance of memory cache. + */ + public function setMemoryCache(\OC\Memcache\Cache $memoryCache = null) { + $this->memoryCache = $memoryCache; + } +} diff --git a/docker/overlays/nextcloud/html/lib/base.php b/docker/overlays/nextcloud/html/lib/base.php new file mode 100644 index 0000000..6b715f9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/base.php @@ -0,0 +1,1092 @@ + + * @author Andreas Fischer + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bernhard Posselt + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christoph Wurst + * @author Damjan Georgievski + * @author Daniel Kesselberg + * @author davidgumberg + * @author Eric Masseran + * @author Florin Peter + * @author Greta Doci + * @author Jakob Sack + * @author jaltek + * @author Jan-Christoph Borchardt + * @author Joachim Sokolowski + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Jose Quinteiro + * @author Juan Pablo Villafáñez + * @author Julius Härtl + * @author Ko- + * @author Lukas Reschke + * @author MartB + * @author Michael Gapczynski + * @author Morris Jobke + * @author Owen Winkler + * @author Phil Davis + * @author Ramiro Aparicio + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sebastian Wessalowski + * @author Stefan Weil + * @author Thomas Müller + * @author Thomas Tanghus + * @author Tobia De Koninck + * @author Vincent Petry + * @author Volkan Gezer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\UserRemovedEvent; +use OCP\ILogger; +use OCP\Share; +use OC\Encryption\HookManager; +use OC\Files\Filesystem; +use OC\Share20\Hooks; + +require_once 'public/Constants.php'; + +/** + * Class that is a namespace for all global OC variables + * No, we can not put this class in its own file because it is used by + * OC_autoload! + */ +class OC { + /** + * Associative array for autoloading. classname => filename + */ + public static $CLASSPATH = []; + /** + * The installation path for Nextcloud on the server (e.g. /srv/http/nextcloud) + */ + public static $SERVERROOT = ''; + /** + * the current request path relative to the Nextcloud root (e.g. files/index.php) + */ + private static $SUBURI = ''; + /** + * the Nextcloud root path for http requests (e.g. nextcloud/) + */ + public static $WEBROOT = ''; + /** + * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and + * web path in 'url' + */ + public static $APPSROOTS = []; + + /** + * @var string + */ + public static $configDir; + + /** + * requested app + */ + public static $REQUESTEDAPP = ''; + + /** + * check if Nextcloud runs in cli mode + */ + public static $CLI = false; + + /** + * @var \OC\Autoloader $loader + */ + public static $loader = null; + + /** @var \Composer\Autoload\ClassLoader $composerAutoloader */ + public static $composerAutoloader = null; + + /** + * @var \OC\Server + */ + public static $server = null; + + /** + * @var \OC\Config + */ + private static $config = null; + + /** + * @throws \RuntimeException when the 3rdparty directory is missing or + * the app path list is empty or contains an invalid path + */ + public static function initPaths() { + if (defined('PHPUNIT_CONFIG_DIR')) { + self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/'; + } elseif (defined('PHPUNIT_RUN') and PHPUNIT_RUN and is_dir(OC::$SERVERROOT . '/tests/config/')) { + self::$configDir = OC::$SERVERROOT . '/tests/config/'; + } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { + self::$configDir = rtrim($dir, '/') . '/'; + } else { + self::$configDir = OC::$SERVERROOT . '/config/'; + } + self::$config = new \OC\Config(self::$configDir); + + OC::$SUBURI = str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"]), strlen(OC::$SERVERROOT))); + /** + * FIXME: The following lines are required because we can't yet instantiate + * \OC::$server->getRequest() since \OC::$server does not yet exist. + */ + $params = [ + 'server' => [ + 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], + 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], + ], + ]; + $fakeRequest = new \OC\AppFramework\Http\Request($params, null, new \OC\AllConfig(new \OC\SystemConfig(self::$config))); + $scriptName = $fakeRequest->getScriptName(); + if (substr($scriptName, -1) == '/') { + $scriptName .= 'index.php'; + //make sure suburi follows the same rules as scriptName + if (substr(OC::$SUBURI, -9) != 'index.php') { + if (substr(OC::$SUBURI, -1) != '/') { + OC::$SUBURI = OC::$SUBURI . '/'; + } + OC::$SUBURI = OC::$SUBURI . 'index.php'; + } + } + + + if (OC::$CLI) { + OC::$WEBROOT = self::$config->getValue('overwritewebroot', ''); + } else { + if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) { + OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI)); + + if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') { + OC::$WEBROOT = '/' . OC::$WEBROOT; + } + } else { + // The scriptName is not ending with OC::$SUBURI + // This most likely means that we are calling from CLI. + // However some cron jobs still need to generate + // a web URL, so we use overwritewebroot as a fallback. + OC::$WEBROOT = self::$config->getValue('overwritewebroot', ''); + } + + // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing + // slash which is required by URL generation. + if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT && + substr($_SERVER['REQUEST_URI'], -1) !== '/') { + header('Location: '.\OC::$WEBROOT.'/'); + exit(); + } + } + + // search the apps folder + $config_paths = self::$config->getValue('apps_paths', []); + if (!empty($config_paths)) { + foreach ($config_paths as $paths) { + if (isset($paths['url']) && isset($paths['path'])) { + $paths['url'] = rtrim($paths['url'], '/'); + $paths['path'] = rtrim($paths['path'], '/'); + OC::$APPSROOTS[] = $paths; + } + } + } elseif (file_exists(OC::$SERVERROOT . '/apps')) { + OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true]; + } elseif (file_exists(OC::$SERVERROOT . '/../apps')) { + OC::$APPSROOTS[] = [ + 'path' => rtrim(dirname(OC::$SERVERROOT), '/') . '/apps', + 'url' => '/apps', + 'writable' => true + ]; + } + + if (empty(OC::$APPSROOTS)) { + throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder' + . ' or the folder above. You can also configure the location in the config.php file.'); + } + $paths = []; + foreach (OC::$APPSROOTS as $path) { + $paths[] = $path['path']; + if (!is_dir($path['path'])) { + throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the' + . ' Nextcloud folder or the folder above. You can also configure the location in the' + . ' config.php file.', $path['path'])); + } + } + + // set the right include path + set_include_path( + implode(PATH_SEPARATOR, $paths) + ); + } + + public static function checkConfig() { + $l = \OC::$server->getL10N('lib'); + + // Create config if it does not already exist + $configFilePath = self::$configDir .'/config.php'; + if (!file_exists($configFilePath)) { + @touch($configFilePath); + } + + // Check if config is writable + $configFileWritable = is_writable($configFilePath); + if (!$configFileWritable && !OC_Helper::isReadOnlyConfigEnabled() + || !$configFileWritable && \OCP\Util::needUpgrade()) { + $urlGenerator = \OC::$server->getURLGenerator(); + + if (self::$CLI) { + echo $l->t('Cannot write into "config" directory!')."\n"; + echo $l->t('This can usually be fixed by giving the webserver write access to the config directory')."\n"; + echo "\n"; + echo $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.')."\n"; + echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ])."\n"; + exit; + } else { + OC_Template::printErrorPage( + $l->t('Cannot write into "config" directory!'), + $l->t('This can usually be fixed by giving the webserver write access to the config directory.') . '. ' + . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s', + [ $urlGenerator->linkToDocs('admin-config') ]), + 503 + ); + } + } + } + + public static function checkInstalled() { + if (defined('OC_CONSOLE')) { + return; + } + // Redirect to installer if not installed + if (!\OC::$server->getSystemConfig()->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') { + if (OC::$CLI) { + throw new Exception('Not installed'); + } else { + $url = OC::$WEBROOT . '/index.php'; + header('Location: ' . $url); + } + exit(); + } + } + + public static function checkMaintenanceMode() { + // Allow ajax update script to execute without being stopped + if (((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') { + // send http status 503 + http_response_code(503); + header('Retry-After: 120'); + + // render error page + $template = new OC_Template('', 'update.user', 'guest'); + OC_Util::addScript('dist/maintenance'); + OC_Util::addStyle('core', 'guest'); + $template->printPage(); + die(); + } + } + + /** + * Prints the upgrade page + * + * @param \OC\SystemConfig $systemConfig + */ + private static function printUpgradePage(\OC\SystemConfig $systemConfig) { + $disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false); + $tooBig = false; + if (!$disableWebUpdater) { + $apps = \OC::$server->getAppManager(); + if ($apps->isInstalled('user_ldap')) { + $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + + $result = $qb->select($qb->func()->count('*', 'user_count')) + ->from('ldap_user_mapping') + ->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + $tooBig = ($row['user_count'] > 50); + } + if (!$tooBig && $apps->isInstalled('user_saml')) { + $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + + $result = $qb->select($qb->func()->count('*', 'user_count')) + ->from('user_saml_users') + ->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + $tooBig = ($row['user_count'] > 50); + } + if (!$tooBig) { + // count users + $stats = \OC::$server->getUserManager()->countUsers(); + $totalUsers = array_sum($stats); + $tooBig = ($totalUsers > 50); + } + } + $ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup']) && + $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis'; + + if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) { + // send http status 503 + http_response_code(503); + header('Retry-After: 120'); + + // render error page + $template = new OC_Template('', 'update.use-cli', 'guest'); + $template->assign('productName', 'nextcloud'); // for now + $template->assign('version', OC_Util::getVersionString()); + $template->assign('tooBig', $tooBig); + + $template->printPage(); + die(); + } + + // check whether this is a core update or apps update + $installedVersion = $systemConfig->getValue('version', '0.0.0'); + $currentVersion = implode('.', \OCP\Util::getVersion()); + + // if not a core upgrade, then it's apps upgrade + $isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '='); + + $oldTheme = $systemConfig->getValue('theme'); + $systemConfig->setValue('theme', ''); + OC_Util::addScript('config'); // needed for web root + OC_Util::addScript('update'); + + /** @var \OC\App\AppManager $appManager */ + $appManager = \OC::$server->getAppManager(); + + $tmpl = new OC_Template('', 'update.admin', 'guest'); + $tmpl->assign('version', OC_Util::getVersionString()); + $tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade); + + // get third party apps + $ocVersion = \OCP\Util::getVersion(); + $ocVersion = implode('.', $ocVersion); + $incompatibleApps = $appManager->getIncompatibleApps($ocVersion); + $incompatibleShippedApps = []; + foreach ($incompatibleApps as $appInfo) { + if ($appManager->isShipped($appInfo['id'])) { + $incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')'; + } + } + + if (!empty($incompatibleShippedApps)) { + $l = \OC::$server->getL10N('core'); + $hint = $l->t('The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.', [implode(', ', $incompatibleShippedApps)]); + throw new \OC\HintException('The files of the app ' . implode(', ', $incompatibleShippedApps) . ' were not replaced correctly. Make sure it is a version compatible with the server.', $hint); + } + + $tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion)); + $tmpl->assign('incompatibleAppsList', $incompatibleApps); + $tmpl->assign('productName', 'Nextcloud'); // for now + $tmpl->assign('oldTheme', $oldTheme); + $tmpl->printPage(); + } + + public static function initSession() { + if (self::$server->getRequest()->getServerProtocol() === 'https') { + ini_set('session.cookie_secure', true); + } + + // prevents javascript from accessing php session cookies + ini_set('session.cookie_httponly', 'true'); + + // set the cookie path to the Nextcloud directory + $cookie_path = OC::$WEBROOT ? : '/'; + ini_set('session.cookie_path', $cookie_path); + + // Let the session name be changed in the initSession Hook + $sessionName = OC_Util::getInstanceId(); + + try { + // set the session name to the instance id - which is unique + $session = new \OC\Session\Internal($sessionName); + + $cryptoWrapper = \OC::$server->getSessionCryptoWrapper(); + $session = $cryptoWrapper->wrapSession($session); + self::$server->setSession($session); + + // if session can't be started break with http 500 error + } catch (Exception $e) { + \OC::$server->getLogger()->logException($e, ['app' => 'base']); + //show the user a detailed error page + OC_Template::printExceptionErrorPage($e, 500); + die(); + } + + $sessionLifeTime = self::getSessionLifeTime(); + + // session timeout + if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) { + if (isset($_COOKIE[session_name()])) { + setcookie(session_name(), '', -1, self::$WEBROOT ? : '/'); + } + \OC::$server->getUserSession()->logout(); + } + + $session->set('LAST_ACTIVITY', time()); + } + + /** + * @return string + */ + private static function getSessionLifeTime() { + return \OC::$server->getConfig()->getSystemValue('session_lifetime', 60 * 60 * 24); + } + + /** + * Try to set some values to the required Nextcloud default + */ + public static function setRequiredIniValues() { + @ini_set('default_charset', 'UTF-8'); + @ini_set('gd.jpeg_ignore_warning', '1'); + } + + /** + * Send the same site cookies + */ + private static function sendSameSiteCookies() { + $cookieParams = session_get_cookie_params(); + $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : ''; + $policies = [ + 'lax', + 'strict', + ]; + + // Append __Host to the cookie if it meets the requirements + $cookiePrefix = ''; + if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') { + $cookiePrefix = '__Host-'; + } + + foreach ($policies as $policy) { + header( + sprintf( + 'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s', + $cookiePrefix, + $policy, + $cookieParams['path'], + $policy + ), + false + ); + } + } + + /** + * Same Site cookie to further mitigate CSRF attacks. This cookie has to + * be set in every request if cookies are sent to add a second level of + * defense against CSRF. + * + * If the cookie is not sent this will set the cookie and reload the page. + * We use an additional cookie since we want to protect logout CSRF and + * also we can't directly interfere with PHP's session mechanism. + */ + private static function performSameSiteCookieProtection() { + $request = \OC::$server->getRequest(); + + // Some user agents are notorious and don't really properly follow HTTP + // specifications. For those, have an automated opt-out. Since the protection + // for remote.php is applied in base.php as starting point we need to opt out + // here. + $incompatibleUserAgents = \OC::$server->getConfig()->getSystemValue('csrf.optout'); + + // Fallback, if csrf.optout is unset + if (!is_array($incompatibleUserAgents)) { + $incompatibleUserAgents = [ + // OS X Finder + '/^WebDAVFS/', + // Windows webdav drive + '/^Microsoft-WebDAV-MiniRedir/', + ]; + } + + if ($request->isUserAgent($incompatibleUserAgents)) { + return; + } + + if (count($_COOKIE) > 0) { + $requestUri = $request->getScriptName(); + $processingScript = explode('/', $requestUri); + $processingScript = $processingScript[count($processingScript)-1]; + + // index.php routes are handled in the middleware + if ($processingScript === 'index.php') { + return; + } + + // All other endpoints require the lax and the strict cookie + if (!$request->passesStrictCookieCheck()) { + self::sendSameSiteCookies(); + // Debug mode gets access to the resources without strict cookie + // due to the fact that the SabreDAV browser also lives there. + if (!\OC::$server->getConfig()->getSystemValue('debug', false)) { + http_response_code(\OCP\AppFramework\Http::STATUS_SERVICE_UNAVAILABLE); + exit(); + } + } + } elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) { + self::sendSameSiteCookies(); + } + } + + public static function init() { + // calculate the root directories + OC::$SERVERROOT = str_replace("\\", '/', substr(__DIR__, 0, -4)); + + // register autoloader + $loaderStart = microtime(true); + require_once __DIR__ . '/autoloader.php'; + self::$loader = new \OC\Autoloader([ + OC::$SERVERROOT . '/lib/private/legacy', + ]); + if (defined('PHPUNIT_RUN')) { + self::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); + } + spl_autoload_register([self::$loader, 'load']); + $loaderEnd = microtime(true); + + self::$CLI = (php_sapi_name() == 'cli'); + + // Add default composer PSR-4 autoloader + self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; + + try { + self::initPaths(); + // setup 3rdparty autoloader + $vendorAutoLoad = OC::$SERVERROOT. '/3rdparty/autoload.php'; + if (!file_exists($vendorAutoLoad)) { + throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".'); + } + require_once $vendorAutoLoad; + } catch (\RuntimeException $e) { + if (!self::$CLI) { + http_response_code(503); + } + // we can't use the template error page here, because this needs the + // DI container which isn't available yet + print($e->getMessage()); + exit(); + } + + // setup the basic server + self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); + self::$server->boot(); + \OC::$server->getEventLogger()->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd); + \OC::$server->getEventLogger()->start('boot', 'Initialize'); + + // Override php.ini and log everything if we're troubleshooting + if (self::$config->getValue('loglevel') === ILogger::DEBUG) { + error_reporting(E_ALL); + } + + // Don't display errors and log them + @ini_set('display_errors', '0'); + @ini_set('log_errors', '1'); + + if (!date_default_timezone_set('UTC')) { + throw new \RuntimeException('Could not set timezone to UTC'); + } + + //try to configure php to enable big file uploads. + //this doesn´t work always depending on the webserver and php configuration. + //Let´s try to overwrite some defaults anyway + + //try to set the maximum execution time to 60min + if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { + @set_time_limit(3600); + } + @ini_set('max_execution_time', '3600'); + @ini_set('max_input_time', '3600'); + + //try to set the maximum filesize to 10G + @ini_set('upload_max_filesize', '10G'); + @ini_set('post_max_size', '10G'); + @ini_set('file_uploads', '50'); + + self::setRequiredIniValues(); + self::handleAuthHeaders(); + self::registerAutoloaderCache(); + + // initialize intl fallback is necessary + \Patchwork\Utf8\Bootup::initIntl(); + OC_Util::isSetLocaleWorking(); + + if (!defined('PHPUNIT_RUN')) { + OC\Log\ErrorHandler::setLogger(\OC::$server->getLogger()); + $debug = \OC::$server->getConfig()->getSystemValue('debug', false); + OC\Log\ErrorHandler::register($debug); + } + + /** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */ + $bootstrapCoordinator = \OC::$server->query(\OC\AppFramework\Bootstrap\Coordinator::class); + $bootstrapCoordinator->runRegistration(); + + \OC::$server->getEventLogger()->start('init_session', 'Initialize session'); + OC_App::loadApps(['session']); + if (!self::$CLI) { + self::initSession(); + } + \OC::$server->getEventLogger()->end('init_session'); + self::checkConfig(); + self::checkInstalled(); + + OC_Response::addSecurityHeaders(); + + self::performSameSiteCookieProtection(); + + if (!defined('OC_CONSOLE')) { + $errors = OC_Util::checkServer(\OC::$server->getSystemConfig()); + if (count($errors) > 0) { + if (!self::$CLI) { + http_response_code(503); + OC_Util::addStyle('guest'); + try { + OC_Template::printGuestPage('', 'error', ['errors' => $errors]); + exit; + } catch (\Exception $e) { + // In case any error happens when showing the error page, we simply fall back to posting the text. + // This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it. + } + } + + // Convert l10n string into regular string for usage in database + $staticErrors = []; + foreach ($errors as $error) { + echo $error['error'] . "\n"; + echo $error['hint'] . "\n\n"; + $staticErrors[] = [ + 'error' => (string)$error['error'], + 'hint' => (string)$error['hint'], + ]; + } + + try { + \OC::$server->getConfig()->setAppValue('core', 'cronErrors', json_encode($staticErrors)); + } catch (\Exception $e) { + echo('Writing to database failed'); + } + exit(1); + } elseif (self::$CLI && \OC::$server->getConfig()->getSystemValue('installed', false)) { + \OC::$server->getConfig()->deleteAppValue('core', 'cronErrors'); + } + } + //try to set the session lifetime + $sessionLifeTime = self::getSessionLifeTime(); + @ini_set('gc_maxlifetime', (string)$sessionLifeTime); + + $systemConfig = \OC::$server->getSystemConfig(); + + // User and Groups + if (!$systemConfig->getValue("installed", false)) { + self::$server->getSession()->set('user_id', ''); + } + + OC_User::useBackend(new \OC\User\Database()); + \OC::$server->getGroupManager()->addBackend(new \OC\Group\Database()); + + // Subscribe to the hook + \OCP\Util::connectHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + '\OC\User\Database', + 'preLoginNameUsedAsUserName' + ); + + //setup extra user backends + if (!\OCP\Util::needUpgrade()) { + OC_User::setupBackends(); + } else { + // Run upgrades in incognito mode + OC_User::setIncognitoMode(true); + } + + self::registerCleanupHooks(); + self::registerFilesystemHooks(); + self::registerShareHooks(); + self::registerEncryptionWrapper(); + self::registerEncryptionHooks(); + self::registerAccountHooks(); + self::registerResourceCollectionHooks(); + self::registerAppRestrictionsHooks(); + + // Make sure that the application class is not loaded before the database is setup + if ($systemConfig->getValue("installed", false)) { + OC_App::loadApp('settings'); + } + + //make sure temporary files are cleaned up + $tmpManager = \OC::$server->getTempManager(); + register_shutdown_function([$tmpManager, 'clean']); + $lockProvider = \OC::$server->getLockingProvider(); + register_shutdown_function([$lockProvider, 'releaseAll']); + + // Check whether the sample configuration has been copied + if ($systemConfig->getValue('copied_sample_config', false)) { + $l = \OC::$server->getL10N('lib'); + OC_Template::printErrorPage( + $l->t('Sample configuration detected'), + $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'), + 503 + ); + return; + } + + $request = \OC::$server->getRequest(); + $host = $request->getInsecureServerHost(); + /** + * if the host passed in headers isn't trusted + * FIXME: Should not be in here at all :see_no_evil: + */ + if (!OC::$CLI + && !\OC::$server->getTrustedDomainHelper()->isTrustedDomain($host) + && self::$server->getConfig()->getSystemValue('installed', false) + ) { + // Allow access to CSS resources + $isScssRequest = false; + if (strpos($request->getPathInfo(), '/css/') === 0) { + $isScssRequest = true; + } + + if (substr($request->getRequestUri(), -11) === '/status.php') { + http_response_code(400); + header('Content-Type: application/json'); + echo '{"error": "Trusted domain error.", "code": 15}'; + exit(); + } + + if (!$isScssRequest) { + http_response_code(400); + + \OC::$server->getLogger()->info( + 'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.', + [ + 'app' => 'core', + 'remoteAddress' => $request->getRemoteAddress(), + 'host' => $host, + ] + ); + + $tmpl = new OCP\Template('core', 'untrustedDomain', 'guest'); + $tmpl->assign('docUrl', \OC::$server->getURLGenerator()->linkToDocs('admin-trusted-domains')); + $tmpl->printPage(); + + exit(); + } + } + \OC::$server->getEventLogger()->end('boot'); + } + + /** + * register hooks for the cleanup of cache and bruteforce protection + */ + public static function registerCleanupHooks() { + //don't try to do this before we are properly setup + if (\OC::$server->getSystemConfig()->getValue('installed', false) && !\OCP\Util::needUpgrade()) { + + // NOTE: This will be replaced to use OCP + $userSession = self::$server->getUserSession(); + $userSession->listen('\OC\User', 'postLogin', function () use ($userSession) { + if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) { + // reset brute force delay for this IP address and username + $uid = \OC::$server->getUserSession()->getUser()->getUID(); + $request = \OC::$server->getRequest(); + $throttler = \OC::$server->getBruteForceThrottler(); + $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]); + } + + try { + $cache = new \OC\Cache\File(); + $cache->gc(); + } catch (\OC\ServerNotAvailableException $e) { + // not a GC exception, pass it on + throw $e; + } catch (\OC\ForbiddenException $e) { + // filesystem blocked for this request, ignore + } catch (\Exception $e) { + // a GC exception should not prevent users from using OC, + // so log the exception + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Exception when running cache gc.', + 'level' => ILogger::WARN, + 'app' => 'core', + ]); + } + }); + } + } + + private static function registerEncryptionWrapper() { + $manager = self::$server->getEncryptionManager(); + \OCP\Util::connectHook('OC_Filesystem', 'preSetup', $manager, 'setupStorage'); + } + + private static function registerEncryptionHooks() { + $enabled = self::$server->getEncryptionManager()->isEnabled(); + if ($enabled) { + \OCP\Util::connectHook(Share::class, 'post_shared', HookManager::class, 'postShared'); + \OCP\Util::connectHook(Share::class, 'post_unshare', HookManager::class, 'postUnshared'); + \OCP\Util::connectHook('OC_Filesystem', 'post_rename', HookManager::class, 'postRename'); + \OCP\Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', HookManager::class, 'postRestore'); + } + } + + private static function registerAccountHooks() { + $hookHandler = new \OC\Accounts\Hooks(\OC::$server->getLogger()); + \OCP\Util::connectHook('OC_User', 'changeUser', $hookHandler, 'changeUserHook'); + } + + private static function registerAppRestrictionsHooks() { + $groupManager = self::$server->query(\OCP\IGroupManager::class); + $groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) { + $appManager = self::$server->getAppManager(); + $apps = $appManager->getEnabledAppsForGroup($group); + foreach ($apps as $appId) { + $restrictions = $appManager->getAppRestriction($appId); + if (empty($restrictions)) { + continue; + } + $key = array_search($group->getGID(), $restrictions); + unset($restrictions[$key]); + $restrictions = array_values($restrictions); + if (empty($restrictions)) { + $appManager->disableApp($appId); + } else { + $appManager->enableAppForGroups($appId, $restrictions); + } + } + }); + } + + private static function registerResourceCollectionHooks() { + \OC\Collaboration\Resources\Listener::register(\OC::$server->getEventDispatcher()); + } + + /** + * register hooks for the filesystem + */ + public static function registerFilesystemHooks() { + // Check for blacklisted files + OC_Hook::connect('OC_Filesystem', 'write', Filesystem::class, 'isBlacklisted'); + OC_Hook::connect('OC_Filesystem', 'rename', Filesystem::class, 'isBlacklisted'); + } + + /** + * register hooks for sharing + */ + public static function registerShareHooks() { + if (\OC::$server->getSystemConfig()->getValue('installed')) { + OC_Hook::connect('OC_User', 'post_deleteUser', Hooks::class, 'post_deleteUser'); + OC_Hook::connect('OC_User', 'post_deleteGroup', Hooks::class, 'post_deleteGroup'); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = \OC::$server->get(IEventDispatcher::class); + $dispatcher->addServiceListener(UserRemovedEvent::class, \OC\Share20\UserRemovedListener::class); + } + } + + protected static function registerAutoloaderCache() { + // The class loader takes an optional low-latency cache, which MUST be + // namespaced. The instanceid is used for namespacing, but might be + // unavailable at this point. Furthermore, it might not be possible to + // generate an instanceid via \OC_Util::getInstanceId() because the + // config file may not be writable. As such, we only register a class + // loader cache if instanceid is available without trying to create one. + $instanceId = \OC::$server->getSystemConfig()->getValue('instanceid', null); + if ($instanceId) { + try { + $memcacheFactory = \OC::$server->getMemCacheFactory(); + self::$loader->setMemoryCache($memcacheFactory->createLocal('Autoloader')); + } catch (\Exception $ex) { + } + } + } + + /** + * Handle the request + */ + public static function handleRequest() { + \OC::$server->getEventLogger()->start('handle_request', 'Handle request'); + $systemConfig = \OC::$server->getSystemConfig(); + + // Check if Nextcloud is installed or in maintenance (update) mode + if (!$systemConfig->getValue('installed', false)) { + \OC::$server->getSession()->clear(); + $setupHelper = new OC\Setup( + $systemConfig, + \OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class), + \OC::$server->getL10N('lib'), + \OC::$server->query(\OCP\Defaults::class), + \OC::$server->getLogger(), + \OC::$server->getSecureRandom(), + \OC::$server->query(\OC\Installer::class) + ); + $controller = new OC\Core\Controller\SetupController($setupHelper); + $controller->run($_POST); + exit(); + } + + $request = \OC::$server->getRequest(); + $requestPath = $request->getRawPathInfo(); + if ($requestPath === '/heartbeat') { + return; + } + if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade + self::checkMaintenanceMode(); + + if (\OCP\Util::needUpgrade()) { + if (function_exists('opcache_reset')) { + opcache_reset(); + } + if (!((bool) $systemConfig->getValue('maintenance', false))) { + self::printUpgradePage($systemConfig); + exit(); + } + } + } + + // emergency app disabling + if ($requestPath === '/disableapp' + && $request->getMethod() === 'POST' + && ((array)$request->getParam('appid')) !== '' + ) { + \OC_JSON::callCheck(); + \OC_JSON::checkAdminUser(); + $appIds = (array)$request->getParam('appid'); + foreach ($appIds as $appId) { + $appId = \OC_App::cleanAppId($appId); + \OC::$server->getAppManager()->disableApp($appId); + } + \OC_JSON::success(); + exit(); + } + + // Always load authentication apps + OC_App::loadApps(['authentication']); + + // Load minimum set of apps + if (!\OCP\Util::needUpgrade() + && !((bool) $systemConfig->getValue('maintenance', false))) { + // For logged-in users: Load everything + if (\OC::$server->getUserSession()->isLoggedIn()) { + OC_App::loadApps(); + } else { + // For guests: Load only filesystem and logging + OC_App::loadApps(['filesystem', 'logging']); + self::handleLogin($request); + } + } + + if (!self::$CLI) { + try { + if (!((bool) $systemConfig->getValue('maintenance', false)) && !\OCP\Util::needUpgrade()) { + OC_App::loadApps(['filesystem', 'logging']); + OC_App::loadApps(); + } + OC_Util::setupFS(); + OC::$server->getRouter()->match(\OC::$server->getRequest()->getRawPathInfo()); + return; + } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { + //header('HTTP/1.0 404 Not Found'); + } catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) { + http_response_code(405); + return; + } + } + + // Handle WebDAV + if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') { + // not allowed any more to prevent people + // mounting this root directly. + // Users need to mount remote.php/webdav instead. + http_response_code(405); + return; + } + + // Someone is logged in + if (\OC::$server->getUserSession()->isLoggedIn()) { + OC_App::loadApps(); + OC_User::setupBackends(); + OC_Util::setupFS(); + // FIXME + // Redirect to default application + OC_Util::redirectToDefaultPage(); + } else { + // Not handled and not logged in + header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute('core.login.showLoginForm')); + } + } + + /** + * Check login: apache auth, auth token, basic auth + * + * @param OCP\IRequest $request + * @return boolean + */ + public static function handleLogin(OCP\IRequest $request) { + $userSession = self::$server->getUserSession(); + if (OC_User::handleApacheAuth()) { + return true; + } + if ($userSession->tryTokenLogin($request)) { + return true; + } + if (isset($_COOKIE['nc_username']) + && isset($_COOKIE['nc_token']) + && isset($_COOKIE['nc_session_id']) + && $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) { + return true; + } + if ($userSession->tryBasicAuthLogin($request, \OC::$server->getBruteForceThrottler())) { + return true; + } + return false; + } + + protected static function handleAuthHeaders() { + //copy http auth headers for apache+php-fcgid work around + if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) { + $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION']; + } + + // Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary. + $vars = [ + 'HTTP_AUTHORIZATION', // apache+php-cgi work around + 'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative + ]; + foreach ($vars as $var) { + if (isset($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) { + $credentials = explode(':', base64_decode($matches[1]), 2); + if (count($credentials) === 2) { + $_SERVER['PHP_AUTH_USER'] = $credentials[0]; + $_SERVER['PHP_AUTH_PW'] = $credentials[1]; + break; + } + } + } + } +} + +OC::init(); diff --git a/docker/overlays/nextcloud/html/lib/composer/autoload.php b/docker/overlays/nextcloud/html/lib/composer/autoload.php new file mode 100644 index 0000000..6de0160 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/LICENSE b/docker/overlays/nextcloud/html/lib/composer/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/autoload_classmap.php b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_classmap.php new file mode 100644 index 0000000..f28e797 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_classmap.php @@ -0,0 +1,1389 @@ + $baseDir . '/lib/public/API.php', + 'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php', + 'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php', + 'OCP\\Accounts\\IAccountProperty' => $baseDir . '/lib/public/Accounts/IAccountProperty.php', + 'OCP\\Accounts\\PropertyDoesNotExistException' => $baseDir . '/lib/public/Accounts/PropertyDoesNotExistException.php', + 'OCP\\Activity\\ActivitySettings' => $baseDir . '/lib/public/Activity/ActivitySettings.php', + 'OCP\\Activity\\IConsumer' => $baseDir . '/lib/public/Activity/IConsumer.php', + 'OCP\\Activity\\IEvent' => $baseDir . '/lib/public/Activity/IEvent.php', + 'OCP\\Activity\\IEventMerger' => $baseDir . '/lib/public/Activity/IEventMerger.php', + 'OCP\\Activity\\IExtension' => $baseDir . '/lib/public/Activity/IExtension.php', + 'OCP\\Activity\\IFilter' => $baseDir . '/lib/public/Activity/IFilter.php', + 'OCP\\Activity\\IManager' => $baseDir . '/lib/public/Activity/IManager.php', + 'OCP\\Activity\\IProvider' => $baseDir . '/lib/public/Activity/IProvider.php', + 'OCP\\Activity\\ISetting' => $baseDir . '/lib/public/Activity/ISetting.php', + 'OCP\\App' => $baseDir . '/lib/public/App.php', + 'OCP\\AppFramework\\ApiController' => $baseDir . '/lib/public/AppFramework/ApiController.php', + 'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php', + 'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php', + 'OCP\\AppFramework\\Bootstrap\\IBootContext' => $baseDir . '/lib/public/AppFramework/Bootstrap/IBootContext.php', + 'OCP\\AppFramework\\Bootstrap\\IBootstrap' => $baseDir . '/lib/public/AppFramework/Bootstrap/IBootstrap.php', + 'OCP\\AppFramework\\Bootstrap\\IRegistrationContext' => $baseDir . '/lib/public/AppFramework/Bootstrap/IRegistrationContext.php', + 'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php', + 'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php', + 'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php', + 'OCP\\AppFramework\\Db\\IMapperException' => $baseDir . '/lib/public/AppFramework/Db/IMapperException.php', + 'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php', + 'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php', + 'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php', + 'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php', + 'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php', + 'OCP\\AppFramework\\Http\\DataDownloadResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDownloadResponse.php', + 'OCP\\AppFramework\\Http\\DataResponse' => $baseDir . '/lib/public/AppFramework/Http/DataResponse.php', + 'OCP\\AppFramework\\Http\\DownloadResponse' => $baseDir . '/lib/public/AppFramework/Http/DownloadResponse.php', + 'OCP\\AppFramework\\Http\\EmptyContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\EmptyFeaturePolicy' => $baseDir . '/lib/public/AppFramework/Http/EmptyFeaturePolicy.php', + 'OCP\\AppFramework\\Http\\Events\\BeforeTemplateRenderedEvent' => $baseDir . '/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php', + 'OCP\\AppFramework\\Http\\FeaturePolicy' => $baseDir . '/lib/public/AppFramework/Http/FeaturePolicy.php', + 'OCP\\AppFramework\\Http\\FileDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/FileDisplayResponse.php', + 'OCP\\AppFramework\\Http\\ICallbackResponse' => $baseDir . '/lib/public/AppFramework/Http/ICallbackResponse.php', + 'OCP\\AppFramework\\Http\\IOutput' => $baseDir . '/lib/public/AppFramework/Http/IOutput.php', + 'OCP\\AppFramework\\Http\\JSONResponse' => $baseDir . '/lib/public/AppFramework/Http/JSONResponse.php', + 'OCP\\AppFramework\\Http\\NotFoundResponse' => $baseDir . '/lib/public/AppFramework/Http/NotFoundResponse.php', + 'OCP\\AppFramework\\Http\\OCSResponse' => $baseDir . '/lib/public/AppFramework/Http/OCSResponse.php', + 'OCP\\AppFramework\\Http\\RedirectResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectResponse.php', + 'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php', + 'OCP\\AppFramework\\Http\\Response' => $baseDir . '/lib/public/AppFramework/Http/Response.php', + 'OCP\\AppFramework\\Http\\StandaloneTemplateResponse' => $baseDir . '/lib/public/AppFramework/Http/StandaloneTemplateResponse.php', + 'OCP\\AppFramework\\Http\\StreamResponse' => $baseDir . '/lib/public/AppFramework/Http/StreamResponse.php', + 'OCP\\AppFramework\\Http\\StrictContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\StrictEvalContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\StrictInlineContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\TemplateResponse' => $baseDir . '/lib/public/AppFramework/Http/TemplateResponse.php', + 'OCP\\AppFramework\\Http\\Template\\ExternalShareMenuAction' => $baseDir . '/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\IMenuAction' => $baseDir . '/lib/public/AppFramework/Http/Template/IMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\LinkMenuAction' => $baseDir . '/lib/public/AppFramework/Http/Template/LinkMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\PublicTemplateResponse' => $baseDir . '/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php', + 'OCP\\AppFramework\\Http\\Template\\SimpleMenuAction' => $baseDir . '/lib/public/AppFramework/Http/Template/SimpleMenuAction.php', + 'OCP\\AppFramework\\Http\\TooManyRequestsResponse' => $baseDir . '/lib/public/AppFramework/Http/TooManyRequestsResponse.php', + 'OCP\\AppFramework\\Http\\ZipResponse' => $baseDir . '/lib/public/AppFramework/Http/ZipResponse.php', + 'OCP\\AppFramework\\IAppContainer' => $baseDir . '/lib/public/AppFramework/IAppContainer.php', + 'OCP\\AppFramework\\Middleware' => $baseDir . '/lib/public/AppFramework/Middleware.php', + 'OCP\\AppFramework\\OCSController' => $baseDir . '/lib/public/AppFramework/OCSController.php', + 'OCP\\AppFramework\\OCS\\OCSBadRequestException' => $baseDir . '/lib/public/AppFramework/OCS/OCSBadRequestException.php', + 'OCP\\AppFramework\\OCS\\OCSException' => $baseDir . '/lib/public/AppFramework/OCS/OCSException.php', + 'OCP\\AppFramework\\OCS\\OCSForbiddenException' => $baseDir . '/lib/public/AppFramework/OCS/OCSForbiddenException.php', + 'OCP\\AppFramework\\OCS\\OCSNotFoundException' => $baseDir . '/lib/public/AppFramework/OCS/OCSNotFoundException.php', + 'OCP\\AppFramework\\PublicShareController' => $baseDir . '/lib/public/AppFramework/PublicShareController.php', + 'OCP\\AppFramework\\QueryException' => $baseDir . '/lib/public/AppFramework/QueryException.php', + 'OCP\\AppFramework\\Services\\IAppConfig' => $baseDir . '/lib/public/AppFramework/Services/IAppConfig.php', + 'OCP\\AppFramework\\Services\\IInitialState' => $baseDir . '/lib/public/AppFramework/Services/IInitialState.php', + 'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => $baseDir . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php', + 'OCP\\AppFramework\\Utility\\ITimeFactory' => $baseDir . '/lib/public/AppFramework/Utility/ITimeFactory.php', + 'OCP\\App\\AppPathNotFoundException' => $baseDir . '/lib/public/App/AppPathNotFoundException.php', + 'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php', + 'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.php', + 'OCP\\Authentication\\Events\\LoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/LoginFailedEvent.php', + 'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php', + 'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php', + 'OCP\\Authentication\\IAlternativeLogin' => $baseDir . '/lib/public/Authentication/IAlternativeLogin.php', + 'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php', + 'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php', + 'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php', + 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php', + 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php', + 'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php', + 'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php', + 'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php', + 'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php', + 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', + 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', + 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderDisabled' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderDisabled.php', + 'OCP\\AutoloadNotAllowedException' => $baseDir . '/lib/public/AutoloadNotAllowedException.php', + 'OCP\\BackgroundJob' => $baseDir . '/lib/public/BackgroundJob.php', + 'OCP\\BackgroundJob\\IJob' => $baseDir . '/lib/public/BackgroundJob/IJob.php', + 'OCP\\BackgroundJob\\IJobList' => $baseDir . '/lib/public/BackgroundJob/IJobList.php', + 'OCP\\BackgroundJob\\Job' => $baseDir . '/lib/public/BackgroundJob/Job.php', + 'OCP\\BackgroundJob\\QueuedJob' => $baseDir . '/lib/public/BackgroundJob/QueuedJob.php', + 'OCP\\BackgroundJob\\TimedJob' => $baseDir . '/lib/public/BackgroundJob/TimedJob.php', + 'OCP\\Broadcast\\Events\\IBroadcastEvent' => $baseDir . '/lib/public/Broadcast/Events/IBroadcastEvent.php', + 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', + 'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php', + 'OCP\\Calendar\\IManager' => $baseDir . '/lib/public/Calendar/IManager.php', + 'OCP\\Calendar\\IMetadataProvider' => $baseDir . '/lib/public/Calendar/IMetadataProvider.php', + 'OCP\\Calendar\\Resource\\IBackend' => $baseDir . '/lib/public/Calendar/Resource/IBackend.php', + 'OCP\\Calendar\\Resource\\IManager' => $baseDir . '/lib/public/Calendar/Resource/IManager.php', + 'OCP\\Calendar\\Resource\\IResource' => $baseDir . '/lib/public/Calendar/Resource/IResource.php', + 'OCP\\Calendar\\Resource\\IResourceMetadata' => $baseDir . '/lib/public/Calendar/Resource/IResourceMetadata.php', + 'OCP\\Calendar\\Room\\IBackend' => $baseDir . '/lib/public/Calendar/Room/IBackend.php', + 'OCP\\Calendar\\Room\\IManager' => $baseDir . '/lib/public/Calendar/Room/IManager.php', + 'OCP\\Calendar\\Room\\IRoom' => $baseDir . '/lib/public/Calendar/Room/IRoom.php', + 'OCP\\Calendar\\Room\\IRoomMetadata' => $baseDir . '/lib/public/Calendar/Room/IRoomMetadata.php', + 'OCP\\Capabilities\\ICapability' => $baseDir . '/lib/public/Capabilities/ICapability.php', + 'OCP\\Capabilities\\IPublicCapability' => $baseDir . '/lib/public/Capabilities/IPublicCapability.php', + 'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => $baseDir . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php', + 'OCP\\Collaboration\\AutoComplete\\IManager' => $baseDir . '/lib/public/Collaboration/AutoComplete/IManager.php', + 'OCP\\Collaboration\\AutoComplete\\ISorter' => $baseDir . '/lib/public/Collaboration/AutoComplete/ISorter.php', + 'OCP\\Collaboration\\Collaborators\\ISearch' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearch.php', + 'OCP\\Collaboration\\Collaborators\\ISearchPlugin' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearchPlugin.php', + 'OCP\\Collaboration\\Collaborators\\ISearchResult' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearchResult.php', + 'OCP\\Collaboration\\Collaborators\\SearchResultType' => $baseDir . '/lib/public/Collaboration/Collaborators/SearchResultType.php', + 'OCP\\Collaboration\\Resources\\CollectionException' => $baseDir . '/lib/public/Collaboration/Resources/CollectionException.php', + 'OCP\\Collaboration\\Resources\\ICollection' => $baseDir . '/lib/public/Collaboration/Resources/ICollection.php', + 'OCP\\Collaboration\\Resources\\IManager' => $baseDir . '/lib/public/Collaboration/Resources/IManager.php', + 'OCP\\Collaboration\\Resources\\IProvider' => $baseDir . '/lib/public/Collaboration/Resources/IProvider.php', + 'OCP\\Collaboration\\Resources\\IProviderManager' => $baseDir . '/lib/public/Collaboration/Resources/IProviderManager.php', + 'OCP\\Collaboration\\Resources\\IResource' => $baseDir . '/lib/public/Collaboration/Resources/IResource.php', + 'OCP\\Collaboration\\Resources\\ResourceException' => $baseDir . '/lib/public/Collaboration/Resources/ResourceException.php', + 'OCP\\Command\\IBus' => $baseDir . '/lib/public/Command/IBus.php', + 'OCP\\Command\\ICommand' => $baseDir . '/lib/public/Command/ICommand.php', + 'OCP\\Comments\\CommentsEntityEvent' => $baseDir . '/lib/public/Comments/CommentsEntityEvent.php', + 'OCP\\Comments\\CommentsEvent' => $baseDir . '/lib/public/Comments/CommentsEvent.php', + 'OCP\\Comments\\IComment' => $baseDir . '/lib/public/Comments/IComment.php', + 'OCP\\Comments\\ICommentsEventHandler' => $baseDir . '/lib/public/Comments/ICommentsEventHandler.php', + 'OCP\\Comments\\ICommentsManager' => $baseDir . '/lib/public/Comments/ICommentsManager.php', + 'OCP\\Comments\\ICommentsManagerFactory' => $baseDir . '/lib/public/Comments/ICommentsManagerFactory.php', + 'OCP\\Comments\\IllegalIDChangeException' => $baseDir . '/lib/public/Comments/IllegalIDChangeException.php', + 'OCP\\Comments\\MessageTooLongException' => $baseDir . '/lib/public/Comments/MessageTooLongException.php', + 'OCP\\Comments\\NotFoundException' => $baseDir . '/lib/public/Comments/NotFoundException.php', + 'OCP\\Console\\ConsoleEvent' => $baseDir . '/lib/public/Console/ConsoleEvent.php', + 'OCP\\Constants' => $baseDir . '/lib/public/Constants.php', + 'OCP\\Contacts\\ContactsMenu\\IAction' => $baseDir . '/lib/public/Contacts/ContactsMenu/IAction.php', + 'OCP\\Contacts\\ContactsMenu\\IActionFactory' => $baseDir . '/lib/public/Contacts/ContactsMenu/IActionFactory.php', + 'OCP\\Contacts\\ContactsMenu\\IContactsStore' => $baseDir . '/lib/public/Contacts/ContactsMenu/IContactsStore.php', + 'OCP\\Contacts\\ContactsMenu\\IEntry' => $baseDir . '/lib/public/Contacts/ContactsMenu/IEntry.php', + 'OCP\\Contacts\\ContactsMenu\\ILinkAction' => $baseDir . '/lib/public/Contacts/ContactsMenu/ILinkAction.php', + 'OCP\\Contacts\\ContactsMenu\\IProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IProvider.php', + 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', + 'OCP\\Contacts\\IManager' => $baseDir . '/lib/public/Contacts/IManager.php', + 'OCP\\DB\\ISchemaWrapper' => $baseDir . '/lib/public/DB/ISchemaWrapper.php', + 'OCP\\DB\\QueryBuilder\\ICompositeExpression' => $baseDir . '/lib/public/DB/QueryBuilder/ICompositeExpression.php', + 'OCP\\DB\\QueryBuilder\\IExpressionBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/IExpressionBuilder.php', + 'OCP\\DB\\QueryBuilder\\IFunctionBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/IFunctionBuilder.php', + 'OCP\\DB\\QueryBuilder\\ILiteral' => $baseDir . '/lib/public/DB/QueryBuilder/ILiteral.php', + 'OCP\\DB\\QueryBuilder\\IParameter' => $baseDir . '/lib/public/DB/QueryBuilder/IParameter.php', + 'OCP\\DB\\QueryBuilder\\IQueryBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/IQueryBuilder.php', + 'OCP\\DB\\QueryBuilder\\IQueryFunction' => $baseDir . '/lib/public/DB/QueryBuilder/IQueryFunction.php', + 'OCP\\Dashboard\\Exceptions\\DashboardAppNotAvailableException' => $baseDir . '/lib/public/Dashboard/Exceptions/DashboardAppNotAvailableException.php', + 'OCP\\Dashboard\\IDashboardManager' => $baseDir . '/lib/public/Dashboard/IDashboardManager.php', + 'OCP\\Dashboard\\IDashboardWidget' => $baseDir . '/lib/public/Dashboard/IDashboardWidget.php', + 'OCP\\Dashboard\\IManager' => $baseDir . '/lib/public/Dashboard/IManager.php', + 'OCP\\Dashboard\\IWidget' => $baseDir . '/lib/public/Dashboard/IWidget.php', + 'OCP\\Dashboard\\Model\\IWidgetConfig' => $baseDir . '/lib/public/Dashboard/Model/IWidgetConfig.php', + 'OCP\\Dashboard\\Model\\IWidgetRequest' => $baseDir . '/lib/public/Dashboard/Model/IWidgetRequest.php', + 'OCP\\Dashboard\\Model\\WidgetSetting' => $baseDir . '/lib/public/Dashboard/Model/WidgetSetting.php', + 'OCP\\Dashboard\\Model\\WidgetSetup' => $baseDir . '/lib/public/Dashboard/Model/WidgetSetup.php', + 'OCP\\Dashboard\\Model\\WidgetTemplate' => $baseDir . '/lib/public/Dashboard/Model/WidgetTemplate.php', + 'OCP\\Dashboard\\RegisterWidgetEvent' => $baseDir . '/lib/public/Dashboard/RegisterWidgetEvent.php', + 'OCP\\Dashboard\\Service\\IEventsService' => $baseDir . '/lib/public/Dashboard/Service/IEventsService.php', + 'OCP\\Dashboard\\Service\\IWidgetsService' => $baseDir . '/lib/public/Dashboard/Service/IWidgetsService.php', + 'OCP\\Defaults' => $baseDir . '/lib/public/Defaults.php', + 'OCP\\Diagnostics\\IEvent' => $baseDir . '/lib/public/Diagnostics/IEvent.php', + 'OCP\\Diagnostics\\IEventLogger' => $baseDir . '/lib/public/Diagnostics/IEventLogger.php', + 'OCP\\Diagnostics\\IQuery' => $baseDir . '/lib/public/Diagnostics/IQuery.php', + 'OCP\\Diagnostics\\IQueryLogger' => $baseDir . '/lib/public/Diagnostics/IQueryLogger.php', + 'OCP\\DirectEditing\\ACreateEmpty' => $baseDir . '/lib/public/DirectEditing/ACreateEmpty.php', + 'OCP\\DirectEditing\\ACreateFromTemplate' => $baseDir . '/lib/public/DirectEditing/ACreateFromTemplate.php', + 'OCP\\DirectEditing\\ATemplate' => $baseDir . '/lib/public/DirectEditing/ATemplate.php', + 'OCP\\DirectEditing\\IEditor' => $baseDir . '/lib/public/DirectEditing/IEditor.php', + 'OCP\\DirectEditing\\IManager' => $baseDir . '/lib/public/DirectEditing/IManager.php', + 'OCP\\DirectEditing\\IToken' => $baseDir . '/lib/public/DirectEditing/IToken.php', + 'OCP\\DirectEditing\\RegisterDirectEditorEvent' => $baseDir . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php', + 'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => $baseDir . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php', + 'OCP\\Encryption\\IEncryptionModule' => $baseDir . '/lib/public/Encryption/IEncryptionModule.php', + 'OCP\\Encryption\\IFile' => $baseDir . '/lib/public/Encryption/IFile.php', + 'OCP\\Encryption\\IManager' => $baseDir . '/lib/public/Encryption/IManager.php', + 'OCP\\Encryption\\Keys\\IStorage' => $baseDir . '/lib/public/Encryption/Keys/IStorage.php', + 'OCP\\EventDispatcher\\ABroadcastedEvent' => $baseDir . '/lib/public/EventDispatcher/ABroadcastedEvent.php', + 'OCP\\EventDispatcher\\Event' => $baseDir . '/lib/public/EventDispatcher/Event.php', + 'OCP\\EventDispatcher\\GenericEvent' => $baseDir . '/lib/public/EventDispatcher/GenericEvent.php', + 'OCP\\EventDispatcher\\IEventDispatcher' => $baseDir . '/lib/public/EventDispatcher/IEventDispatcher.php', + 'OCP\\EventDispatcher\\IEventListener' => $baseDir . '/lib/public/EventDispatcher/IEventListener.php', + 'OCP\\Federation\\Exceptions\\ActionNotSupportedException' => $baseDir . '/lib/public/Federation/Exceptions/ActionNotSupportedException.php', + 'OCP\\Federation\\Exceptions\\AuthenticationFailedException' => $baseDir . '/lib/public/Federation/Exceptions/AuthenticationFailedException.php', + 'OCP\\Federation\\Exceptions\\BadRequestException' => $baseDir . '/lib/public/Federation/Exceptions/BadRequestException.php', + 'OCP\\Federation\\Exceptions\\ProviderAlreadyExistsException' => $baseDir . '/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php', + 'OCP\\Federation\\Exceptions\\ProviderCouldNotAddShareException' => $baseDir . '/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php', + 'OCP\\Federation\\Exceptions\\ProviderDoesNotExistsException' => $baseDir . '/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php', + 'OCP\\Federation\\ICloudFederationFactory' => $baseDir . '/lib/public/Federation/ICloudFederationFactory.php', + 'OCP\\Federation\\ICloudFederationNotification' => $baseDir . '/lib/public/Federation/ICloudFederationNotification.php', + 'OCP\\Federation\\ICloudFederationProvider' => $baseDir . '/lib/public/Federation/ICloudFederationProvider.php', + 'OCP\\Federation\\ICloudFederationProviderManager' => $baseDir . '/lib/public/Federation/ICloudFederationProviderManager.php', + 'OCP\\Federation\\ICloudFederationShare' => $baseDir . '/lib/public/Federation/ICloudFederationShare.php', + 'OCP\\Federation\\ICloudId' => $baseDir . '/lib/public/Federation/ICloudId.php', + 'OCP\\Federation\\ICloudIdManager' => $baseDir . '/lib/public/Federation/ICloudIdManager.php', + 'OCP\\Files' => $baseDir . '/lib/public/Files.php', + 'OCP\\Files\\AlreadyExistsException' => $baseDir . '/lib/public/Files/AlreadyExistsException.php', + 'OCP\\Files\\Cache\\CacheInsertEvent' => $baseDir . '/lib/public/Files/Cache/CacheInsertEvent.php', + 'OCP\\Files\\Cache\\CacheUpdateEvent' => $baseDir . '/lib/public/Files/Cache/CacheUpdateEvent.php', + 'OCP\\Files\\Cache\\ICache' => $baseDir . '/lib/public/Files/Cache/ICache.php', + 'OCP\\Files\\Cache\\ICacheEntry' => $baseDir . '/lib/public/Files/Cache/ICacheEntry.php', + 'OCP\\Files\\Cache\\ICacheEvent' => $baseDir . '/lib/public/Files/Cache/ICacheEvent.php', + 'OCP\\Files\\Cache\\IPropagator' => $baseDir . '/lib/public/Files/Cache/IPropagator.php', + 'OCP\\Files\\Cache\\IScanner' => $baseDir . '/lib/public/Files/Cache/IScanner.php', + 'OCP\\Files\\Cache\\IUpdater' => $baseDir . '/lib/public/Files/Cache/IUpdater.php', + 'OCP\\Files\\Cache\\IWatcher' => $baseDir . '/lib/public/Files/Cache/IWatcher.php', + 'OCP\\Files\\Config\\ICachedMountFileInfo' => $baseDir . '/lib/public/Files/Config/ICachedMountFileInfo.php', + 'OCP\\Files\\Config\\ICachedMountInfo' => $baseDir . '/lib/public/Files/Config/ICachedMountInfo.php', + 'OCP\\Files\\Config\\IHomeMountProvider' => $baseDir . '/lib/public/Files/Config/IHomeMountProvider.php', + 'OCP\\Files\\Config\\IMountProvider' => $baseDir . '/lib/public/Files/Config/IMountProvider.php', + 'OCP\\Files\\Config\\IMountProviderCollection' => $baseDir . '/lib/public/Files/Config/IMountProviderCollection.php', + 'OCP\\Files\\Config\\IRootMountProvider' => $baseDir . '/lib/public/Files/Config/IRootMountProvider.php', + 'OCP\\Files\\Config\\IUserMountCache' => $baseDir . '/lib/public/Files/Config/IUserMountCache.php', + 'OCP\\Files\\EmptyFileNameException' => $baseDir . '/lib/public/Files/EmptyFileNameException.php', + 'OCP\\Files\\EntityTooLargeException' => $baseDir . '/lib/public/Files/EntityTooLargeException.php', + 'OCP\\Files\\Events\\BeforeFileScannedEvent' => $baseDir . '/lib/public/Files/Events/BeforeFileScannedEvent.php', + 'OCP\\Files\\Events\\BeforeFolderScannedEvent' => $baseDir . '/lib/public/Files/Events/BeforeFolderScannedEvent.php', + 'OCP\\Files\\Events\\FileCacheUpdated' => $baseDir . '/lib/public/Files/Events/FileCacheUpdated.php', + 'OCP\\Files\\Events\\FileScannedEvent' => $baseDir . '/lib/public/Files/Events/FileScannedEvent.php', + 'OCP\\Files\\Events\\FolderScannedEvent' => $baseDir . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\NodeAddedToCache' => $baseDir . '/lib/public/Files/Events/NodeAddedToCache.php', + 'OCP\\Files\\Events\\NodeRemovedFromCache' => $baseDir . '/lib/public/Files/Events/NodeRemovedFromCache.php', + 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => $baseDir . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', + 'OCP\\Files\\Events\\Node\\AbstractNodesEvent' => $baseDir . '/lib/public/Files/Events/Node/AbstractNodesEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeCopiedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeCopiedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeCreatedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeCreatedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeReadEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeReadEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeWrittenEvent.php', + 'OCP\\Files\\Events\\Node\\NodeCopiedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeCopiedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeCreatedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeCreatedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeDeletedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeDeletedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeRenamedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeWrittenEvent.php', + 'OCP\\Files\\File' => $baseDir . '/lib/public/Files/File.php', + 'OCP\\Files\\FileInfo' => $baseDir . '/lib/public/Files/FileInfo.php', + 'OCP\\Files\\FileNameTooLongException' => $baseDir . '/lib/public/Files/FileNameTooLongException.php', + 'OCP\\Files\\Folder' => $baseDir . '/lib/public/Files/Folder.php', + 'OCP\\Files\\ForbiddenException' => $baseDir . '/lib/public/Files/ForbiddenException.php', + 'OCP\\Files\\GenericFileException' => $baseDir . '/lib/public/Files/GenericFileException.php', + 'OCP\\Files\\IAppData' => $baseDir . '/lib/public/Files/IAppData.php', + 'OCP\\Files\\IHomeStorage' => $baseDir . '/lib/public/Files/IHomeStorage.php', + 'OCP\\Files\\IMimeTypeDetector' => $baseDir . '/lib/public/Files/IMimeTypeDetector.php', + 'OCP\\Files\\IMimeTypeLoader' => $baseDir . '/lib/public/Files/IMimeTypeLoader.php', + 'OCP\\Files\\IRootFolder' => $baseDir . '/lib/public/Files/IRootFolder.php', + 'OCP\\Files\\InvalidCharacterInPathException' => $baseDir . '/lib/public/Files/InvalidCharacterInPathException.php', + 'OCP\\Files\\InvalidContentException' => $baseDir . '/lib/public/Files/InvalidContentException.php', + 'OCP\\Files\\InvalidDirectoryException' => $baseDir . '/lib/public/Files/InvalidDirectoryException.php', + 'OCP\\Files\\InvalidPathException' => $baseDir . '/lib/public/Files/InvalidPathException.php', + 'OCP\\Files\\LockNotAcquiredException' => $baseDir . '/lib/public/Files/LockNotAcquiredException.php', + 'OCP\\Files\\Mount\\IMountManager' => $baseDir . '/lib/public/Files/Mount/IMountManager.php', + 'OCP\\Files\\Mount\\IMountPoint' => $baseDir . '/lib/public/Files/Mount/IMountPoint.php', + 'OCP\\Files\\Node' => $baseDir . '/lib/public/Files/Node.php', + 'OCP\\Files\\NotEnoughSpaceException' => $baseDir . '/lib/public/Files/NotEnoughSpaceException.php', + 'OCP\\Files\\NotFoundException' => $baseDir . '/lib/public/Files/NotFoundException.php', + 'OCP\\Files\\NotPermittedException' => $baseDir . '/lib/public/Files/NotPermittedException.php', + 'OCP\\Files\\Notify\\IChange' => $baseDir . '/lib/public/Files/Notify/IChange.php', + 'OCP\\Files\\Notify\\INotifyHandler' => $baseDir . '/lib/public/Files/Notify/INotifyHandler.php', + 'OCP\\Files\\Notify\\IRenameChange' => $baseDir . '/lib/public/Files/Notify/IRenameChange.php', + 'OCP\\Files\\ObjectStore\\IObjectStore' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ReservedWordException' => $baseDir . '/lib/public/Files/ReservedWordException.php', + 'OCP\\Files\\Search\\ISearchBinaryOperator' => $baseDir . '/lib/public/Files/Search/ISearchBinaryOperator.php', + 'OCP\\Files\\Search\\ISearchComparison' => $baseDir . '/lib/public/Files/Search/ISearchComparison.php', + 'OCP\\Files\\Search\\ISearchOperator' => $baseDir . '/lib/public/Files/Search/ISearchOperator.php', + 'OCP\\Files\\Search\\ISearchOrder' => $baseDir . '/lib/public/Files/Search/ISearchOrder.php', + 'OCP\\Files\\Search\\ISearchQuery' => $baseDir . '/lib/public/Files/Search/ISearchQuery.php', + 'OCP\\Files\\SimpleFS\\ISimpleFile' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFile.php', + 'OCP\\Files\\SimpleFS\\ISimpleFolder' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFolder.php', + 'OCP\\Files\\SimpleFS\\ISimpleRoot' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleRoot.php', + 'OCP\\Files\\SimpleFS\\InMemoryFile' => $baseDir . '/lib/public/Files/SimpleFS/InMemoryFile.php', + 'OCP\\Files\\Storage' => $baseDir . '/lib/public/Files/Storage.php', + 'OCP\\Files\\StorageAuthException' => $baseDir . '/lib/public/Files/StorageAuthException.php', + 'OCP\\Files\\StorageBadConfigException' => $baseDir . '/lib/public/Files/StorageBadConfigException.php', + 'OCP\\Files\\StorageConnectionException' => $baseDir . '/lib/public/Files/StorageConnectionException.php', + 'OCP\\Files\\StorageInvalidException' => $baseDir . '/lib/public/Files/StorageInvalidException.php', + 'OCP\\Files\\StorageNotAvailableException' => $baseDir . '/lib/public/Files/StorageNotAvailableException.php', + 'OCP\\Files\\StorageTimeoutException' => $baseDir . '/lib/public/Files/StorageTimeoutException.php', + 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => $baseDir . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', + 'OCP\\Files\\Storage\\ILockingStorage' => $baseDir . '/lib/public/Files/Storage/ILockingStorage.php', + 'OCP\\Files\\Storage\\INotifyStorage' => $baseDir . '/lib/public/Files/Storage/INotifyStorage.php', + 'OCP\\Files\\Storage\\IStorage' => $baseDir . '/lib/public/Files/Storage/IStorage.php', + 'OCP\\Files\\Storage\\IStorageFactory' => $baseDir . '/lib/public/Files/Storage/IStorageFactory.php', + 'OCP\\Files\\Storage\\IWriteStreamStorage' => $baseDir . '/lib/public/Files/Storage/IWriteStreamStorage.php', + 'OCP\\Files\\UnseekableException' => $baseDir . '/lib/public/Files/UnseekableException.php', + 'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => $baseDir . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php', + 'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => $baseDir . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php', + 'OCP\\FullTextSearch\\IFullTextSearchManager' => $baseDir . '/lib/public/FullTextSearch/IFullTextSearchManager.php', + 'OCP\\FullTextSearch\\IFullTextSearchPlatform' => $baseDir . '/lib/public/FullTextSearch/IFullTextSearchPlatform.php', + 'OCP\\FullTextSearch\\IFullTextSearchProvider' => $baseDir . '/lib/public/FullTextSearch/IFullTextSearchProvider.php', + 'OCP\\FullTextSearch\\Model\\IDocumentAccess' => $baseDir . '/lib/public/FullTextSearch/Model/IDocumentAccess.php', + 'OCP\\FullTextSearch\\Model\\IIndex' => $baseDir . '/lib/public/FullTextSearch/Model/IIndex.php', + 'OCP\\FullTextSearch\\Model\\IIndexDocument' => $baseDir . '/lib/public/FullTextSearch/Model/IIndexDocument.php', + 'OCP\\FullTextSearch\\Model\\IIndexOptions' => $baseDir . '/lib/public/FullTextSearch/Model/IIndexOptions.php', + 'OCP\\FullTextSearch\\Model\\IRunner' => $baseDir . '/lib/public/FullTextSearch/Model/IRunner.php', + 'OCP\\FullTextSearch\\Model\\ISearchOption' => $baseDir . '/lib/public/FullTextSearch/Model/ISearchOption.php', + 'OCP\\FullTextSearch\\Model\\ISearchRequest' => $baseDir . '/lib/public/FullTextSearch/Model/ISearchRequest.php', + 'OCP\\FullTextSearch\\Model\\ISearchRequestSimpleQuery' => $baseDir . '/lib/public/FullTextSearch/Model/ISearchRequestSimpleQuery.php', + 'OCP\\FullTextSearch\\Model\\ISearchResult' => $baseDir . '/lib/public/FullTextSearch/Model/ISearchResult.php', + 'OCP\\FullTextSearch\\Model\\ISearchTemplate' => $baseDir . '/lib/public/FullTextSearch/Model/ISearchTemplate.php', + 'OCP\\FullTextSearch\\Service\\IIndexService' => $baseDir . '/lib/public/FullTextSearch/Service/IIndexService.php', + 'OCP\\FullTextSearch\\Service\\IProviderService' => $baseDir . '/lib/public/FullTextSearch/Service/IProviderService.php', + 'OCP\\FullTextSearch\\Service\\ISearchService' => $baseDir . '/lib/public/FullTextSearch/Service/ISearchService.php', + 'OCP\\GlobalScale\\IConfig' => $baseDir . '/lib/public/GlobalScale/IConfig.php', + 'OCP\\GroupInterface' => $baseDir . '/lib/public/GroupInterface.php', + 'OCP\\Group\\Backend\\ABackend' => $baseDir . '/lib/public/Group/Backend/ABackend.php', + 'OCP\\Group\\Backend\\IAddToGroupBackend' => $baseDir . '/lib/public/Group/Backend/IAddToGroupBackend.php', + 'OCP\\Group\\Backend\\ICountDisabledInGroup' => $baseDir . '/lib/public/Group/Backend/ICountDisabledInGroup.php', + 'OCP\\Group\\Backend\\ICountUsersBackend' => $baseDir . '/lib/public/Group/Backend/ICountUsersBackend.php', + 'OCP\\Group\\Backend\\ICreateGroupBackend' => $baseDir . '/lib/public/Group/Backend/ICreateGroupBackend.php', + 'OCP\\Group\\Backend\\IDeleteGroupBackend' => $baseDir . '/lib/public/Group/Backend/IDeleteGroupBackend.php', + 'OCP\\Group\\Backend\\IGetDisplayNameBackend' => $baseDir . '/lib/public/Group/Backend/IGetDisplayNameBackend.php', + 'OCP\\Group\\Backend\\IGroupDetailsBackend' => $baseDir . '/lib/public/Group/Backend/IGroupDetailsBackend.php', + 'OCP\\Group\\Backend\\IHideFromCollaborationBackend' => $baseDir . '/lib/public/Group/Backend/IHideFromCollaborationBackend.php', + 'OCP\\Group\\Backend\\IIsAdminBackend' => $baseDir . '/lib/public/Group/Backend/IIsAdminBackend.php', + 'OCP\\Group\\Backend\\IRemoveFromGroupBackend' => $baseDir . '/lib/public/Group/Backend/IRemoveFromGroupBackend.php', + 'OCP\\Group\\Backend\\ISetDisplayNameBackend' => $baseDir . '/lib/public/Group/Backend/ISetDisplayNameBackend.php', + 'OCP\\Group\\Events\\BeforeGroupCreatedEvent' => $baseDir . '/lib/public/Group/Events/BeforeGroupCreatedEvent.php', + 'OCP\\Group\\Events\\BeforeGroupDeletedEvent' => $baseDir . '/lib/public/Group/Events/BeforeGroupDeletedEvent.php', + 'OCP\\Group\\Events\\BeforeUserAddedEvent' => $baseDir . '/lib/public/Group/Events/BeforeUserAddedEvent.php', + 'OCP\\Group\\Events\\BeforeUserRemovedEvent' => $baseDir . '/lib/public/Group/Events/BeforeUserRemovedEvent.php', + 'OCP\\Group\\Events\\GroupCreatedEvent' => $baseDir . '/lib/public/Group/Events/GroupCreatedEvent.php', + 'OCP\\Group\\Events\\GroupDeletedEvent' => $baseDir . '/lib/public/Group/Events/GroupDeletedEvent.php', + 'OCP\\Group\\Events\\UserAddedEvent' => $baseDir . '/lib/public/Group/Events/UserAddedEvent.php', + 'OCP\\Group\\Events\\UserRemovedEvent' => $baseDir . '/lib/public/Group/Events/UserRemovedEvent.php', + 'OCP\\Group\\ISubAdmin' => $baseDir . '/lib/public/Group/ISubAdmin.php', + 'OCP\\Http\\Client\\IClient' => $baseDir . '/lib/public/Http/Client/IClient.php', + 'OCP\\Http\\Client\\IClientService' => $baseDir . '/lib/public/Http/Client/IClientService.php', + 'OCP\\Http\\Client\\IResponse' => $baseDir . '/lib/public/Http/Client/IResponse.php', + 'OCP\\Http\\Client\\LocalServerException' => $baseDir . '/lib/public/Http/Client/LocalServerException.php', + 'OCP\\IAddressBook' => $baseDir . '/lib/public/IAddressBook.php', + 'OCP\\IAppConfig' => $baseDir . '/lib/public/IAppConfig.php', + 'OCP\\IAvatar' => $baseDir . '/lib/public/IAvatar.php', + 'OCP\\IAvatarManager' => $baseDir . '/lib/public/IAvatarManager.php', + 'OCP\\ICache' => $baseDir . '/lib/public/ICache.php', + 'OCP\\ICacheFactory' => $baseDir . '/lib/public/ICacheFactory.php', + 'OCP\\ICertificate' => $baseDir . '/lib/public/ICertificate.php', + 'OCP\\ICertificateManager' => $baseDir . '/lib/public/ICertificateManager.php', + 'OCP\\IConfig' => $baseDir . '/lib/public/IConfig.php', + 'OCP\\IContainer' => $baseDir . '/lib/public/IContainer.php', + 'OCP\\IDBConnection' => $baseDir . '/lib/public/IDBConnection.php', + 'OCP\\IDateTimeFormatter' => $baseDir . '/lib/public/IDateTimeFormatter.php', + 'OCP\\IDateTimeZone' => $baseDir . '/lib/public/IDateTimeZone.php', + 'OCP\\IEventSource' => $baseDir . '/lib/public/IEventSource.php', + 'OCP\\IGroup' => $baseDir . '/lib/public/IGroup.php', + 'OCP\\IGroupManager' => $baseDir . '/lib/public/IGroupManager.php', + 'OCP\\IImage' => $baseDir . '/lib/public/IImage.php', + 'OCP\\IInitialStateService' => $baseDir . '/lib/public/IInitialStateService.php', + 'OCP\\IL10N' => $baseDir . '/lib/public/IL10N.php', + 'OCP\\ILogger' => $baseDir . '/lib/public/ILogger.php', + 'OCP\\IMemcache' => $baseDir . '/lib/public/IMemcache.php', + 'OCP\\IMemcacheTTL' => $baseDir . '/lib/public/IMemcacheTTL.php', + 'OCP\\INavigationManager' => $baseDir . '/lib/public/INavigationManager.php', + 'OCP\\IPreview' => $baseDir . '/lib/public/IPreview.php', + 'OCP\\IRequest' => $baseDir . '/lib/public/IRequest.php', + 'OCP\\ISearch' => $baseDir . '/lib/public/ISearch.php', + 'OCP\\IServerContainer' => $baseDir . '/lib/public/IServerContainer.php', + 'OCP\\ISession' => $baseDir . '/lib/public/ISession.php', + 'OCP\\ITagManager' => $baseDir . '/lib/public/ITagManager.php', + 'OCP\\ITags' => $baseDir . '/lib/public/ITags.php', + 'OCP\\ITempManager' => $baseDir . '/lib/public/ITempManager.php', + 'OCP\\IURLGenerator' => $baseDir . '/lib/public/IURLGenerator.php', + 'OCP\\IUser' => $baseDir . '/lib/public/IUser.php', + 'OCP\\IUserBackend' => $baseDir . '/lib/public/IUserBackend.php', + 'OCP\\IUserManager' => $baseDir . '/lib/public/IUserManager.php', + 'OCP\\IUserSession' => $baseDir . '/lib/public/IUserSession.php', + 'OCP\\Image' => $baseDir . '/lib/public/Image.php', + 'OCP\\L10N\\IFactory' => $baseDir . '/lib/public/L10N/IFactory.php', + 'OCP\\L10N\\ILanguageIterator' => $baseDir . '/lib/public/L10N/ILanguageIterator.php', + 'OCP\\LDAP\\IDeletionFlagSupport' => $baseDir . '/lib/public/LDAP/IDeletionFlagSupport.php', + 'OCP\\LDAP\\ILDAPProvider' => $baseDir . '/lib/public/LDAP/ILDAPProvider.php', + 'OCP\\LDAP\\ILDAPProviderFactory' => $baseDir . '/lib/public/LDAP/ILDAPProviderFactory.php', + 'OCP\\Lock\\ILockingProvider' => $baseDir . '/lib/public/Lock/ILockingProvider.php', + 'OCP\\Lock\\LockedException' => $baseDir . '/lib/public/Lock/LockedException.php', + 'OCP\\Lock\\ManuallyLockedException' => $baseDir . '/lib/public/Lock/ManuallyLockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => $baseDir . '/lib/public/Lockdown/ILockdownManager.php', + 'OCP\\Log\\IDataLogger' => $baseDir . '/lib/public/Log/IDataLogger.php', + 'OCP\\Log\\IFileBased' => $baseDir . '/lib/public/Log/IFileBased.php', + 'OCP\\Log\\ILogFactory' => $baseDir . '/lib/public/Log/ILogFactory.php', + 'OCP\\Log\\IWriter' => $baseDir . '/lib/public/Log/IWriter.php', + 'OCP\\Log\\RotationTrait' => $baseDir . '/lib/public/Log/RotationTrait.php', + 'OCP\\Mail\\Events\\BeforeMessageSent' => $baseDir . '/lib/public/Mail/Events/BeforeMessageSent.php', + 'OCP\\Mail\\IAttachment' => $baseDir . '/lib/public/Mail/IAttachment.php', + 'OCP\\Mail\\IEMailTemplate' => $baseDir . '/lib/public/Mail/IEMailTemplate.php', + 'OCP\\Mail\\IMailer' => $baseDir . '/lib/public/Mail/IMailer.php', + 'OCP\\Mail\\IMessage' => $baseDir . '/lib/public/Mail/IMessage.php', + 'OCP\\Migration\\BigIntMigration' => $baseDir . '/lib/public/Migration/BigIntMigration.php', + 'OCP\\Migration\\IMigrationStep' => $baseDir . '/lib/public/Migration/IMigrationStep.php', + 'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php', + 'OCP\\Migration\\IRepairStep' => $baseDir . '/lib/public/Migration/IRepairStep.php', + 'OCP\\Migration\\SimpleMigrationStep' => $baseDir . '/lib/public/Migration/SimpleMigrationStep.php', + 'OCP\\Notification\\AlreadyProcessedException' => $baseDir . '/lib/public/Notification/AlreadyProcessedException.php', + 'OCP\\Notification\\IAction' => $baseDir . '/lib/public/Notification/IAction.php', + 'OCP\\Notification\\IApp' => $baseDir . '/lib/public/Notification/IApp.php', + 'OCP\\Notification\\IDeferrableApp' => $baseDir . '/lib/public/Notification/IDeferrableApp.php', + 'OCP\\Notification\\IDismissableNotifier' => $baseDir . '/lib/public/Notification/IDismissableNotifier.php', + 'OCP\\Notification\\IManager' => $baseDir . '/lib/public/Notification/IManager.php', + 'OCP\\Notification\\INotification' => $baseDir . '/lib/public/Notification/INotification.php', + 'OCP\\Notification\\INotifier' => $baseDir . '/lib/public/Notification/INotifier.php', + 'OCP\\OCS\\IDiscoveryService' => $baseDir . '/lib/public/OCS/IDiscoveryService.php', + 'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php', + 'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php', + 'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php', + 'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php', + 'OCP\\Remote\\Api\\IApiCollection' => $baseDir . '/lib/public/Remote/Api/IApiCollection.php', + 'OCP\\Remote\\Api\\IApiFactory' => $baseDir . '/lib/public/Remote/Api/IApiFactory.php', + 'OCP\\Remote\\Api\\ICapabilitiesApi' => $baseDir . '/lib/public/Remote/Api/ICapabilitiesApi.php', + 'OCP\\Remote\\Api\\IUserApi' => $baseDir . '/lib/public/Remote/Api/IUserApi.php', + 'OCP\\Remote\\ICredentials' => $baseDir . '/lib/public/Remote/ICredentials.php', + 'OCP\\Remote\\IInstance' => $baseDir . '/lib/public/Remote/IInstance.php', + 'OCP\\Remote\\IInstanceFactory' => $baseDir . '/lib/public/Remote/IInstanceFactory.php', + 'OCP\\Remote\\IUser' => $baseDir . '/lib/public/Remote/IUser.php', + 'OCP\\RichObjectStrings\\Definitions' => $baseDir . '/lib/public/RichObjectStrings/Definitions.php', + 'OCP\\RichObjectStrings\\IValidator' => $baseDir . '/lib/public/RichObjectStrings/IValidator.php', + 'OCP\\RichObjectStrings\\InvalidObjectExeption' => $baseDir . '/lib/public/RichObjectStrings/InvalidObjectExeption.php', + 'OCP\\Route\\IRoute' => $baseDir . '/lib/public/Route/IRoute.php', + 'OCP\\Route\\IRouter' => $baseDir . '/lib/public/Route/IRouter.php', + 'OCP\\SabrePluginEvent' => $baseDir . '/lib/public/SabrePluginEvent.php', + 'OCP\\SabrePluginException' => $baseDir . '/lib/public/SabrePluginException.php', + 'OCP\\Search\\IProvider' => $baseDir . '/lib/public/Search/IProvider.php', + 'OCP\\Search\\ISearchQuery' => $baseDir . '/lib/public/Search/ISearchQuery.php', + 'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php', + 'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php', + 'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php', + 'OCP\\Search\\SearchResult' => $baseDir . '/lib/public/Search/SearchResult.php', + 'OCP\\Search\\SearchResultEntry' => $baseDir . '/lib/public/Search/SearchResultEntry.php', + 'OCP\\Security\\Bruteforce\\MaxDelayReached' => $baseDir . '/lib/public/Security/Bruteforce/MaxDelayReached.php', + 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', + 'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', + 'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => $baseDir . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php', + 'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => $baseDir . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', + 'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php', + 'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php', + 'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php', + 'OCP\\Security\\IHasher' => $baseDir . '/lib/public/Security/IHasher.php', + 'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php', + 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', + 'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', + 'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', + 'OCP\\Settings\\ISection' => $baseDir . '/lib/public/Settings/ISection.php', + 'OCP\\Settings\\ISettings' => $baseDir . '/lib/public/Settings/ISettings.php', + 'OCP\\Settings\\ISubAdminSettings' => $baseDir . '/lib/public/Settings/ISubAdminSettings.php', + 'OCP\\Share' => $baseDir . '/lib/public/Share.php', + 'OCP\\Share\\Events\\ShareCreatedEvent' => $baseDir . '/lib/public/Share/Events/ShareCreatedEvent.php', + 'OCP\\Share\\Events\\VerifyMountPointEvent' => $baseDir . '/lib/public/Share/Events/VerifyMountPointEvent.php', + 'OCP\\Share\\Exceptions\\GenericShareException' => $baseDir . '/lib/public/Share/Exceptions/GenericShareException.php', + 'OCP\\Share\\Exceptions\\IllegalIDChangeException' => $baseDir . '/lib/public/Share/Exceptions/IllegalIDChangeException.php', + 'OCP\\Share\\Exceptions\\ShareNotFound' => $baseDir . '/lib/public/Share/Exceptions/ShareNotFound.php', + 'OCP\\Share\\IManager' => $baseDir . '/lib/public/Share/IManager.php', + 'OCP\\Share\\IProviderFactory' => $baseDir . '/lib/public/Share/IProviderFactory.php', + 'OCP\\Share\\IShare' => $baseDir . '/lib/public/Share/IShare.php', + 'OCP\\Share\\IShareHelper' => $baseDir . '/lib/public/Share/IShareHelper.php', + 'OCP\\Share\\IShareProvider' => $baseDir . '/lib/public/Share/IShareProvider.php', + 'OCP\\Share_Backend' => $baseDir . '/lib/public/Share_Backend.php', + 'OCP\\Share_Backend_Collection' => $baseDir . '/lib/public/Share_Backend_Collection.php', + 'OCP\\Share_Backend_File_Dependent' => $baseDir . '/lib/public/Share_Backend_File_Dependent.php', + 'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => $baseDir . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php', + 'OCP\\Support\\CrashReport\\IMessageReporter' => $baseDir . '/lib/public/Support/CrashReport/IMessageReporter.php', + 'OCP\\Support\\CrashReport\\IRegistry' => $baseDir . '/lib/public/Support/CrashReport/IRegistry.php', + 'OCP\\Support\\CrashReport\\IReporter' => $baseDir . '/lib/public/Support/CrashReport/IReporter.php', + 'OCP\\Support\\Subscription\\Exception\\AlreadyRegisteredException' => $baseDir . '/lib/public/Support/Subscription/Exception/AlreadyRegisteredException.php', + 'OCP\\Support\\Subscription\\IRegistry' => $baseDir . '/lib/public/Support/Subscription/IRegistry.php', + 'OCP\\Support\\Subscription\\ISubscription' => $baseDir . '/lib/public/Support/Subscription/ISubscription.php', + 'OCP\\Support\\Subscription\\ISupportedApps' => $baseDir . '/lib/public/Support/Subscription/ISupportedApps.php', + 'OCP\\SystemTag\\ISystemTag' => $baseDir . '/lib/public/SystemTag/ISystemTag.php', + 'OCP\\SystemTag\\ISystemTagManager' => $baseDir . '/lib/public/SystemTag/ISystemTagManager.php', + 'OCP\\SystemTag\\ISystemTagManagerFactory' => $baseDir . '/lib/public/SystemTag/ISystemTagManagerFactory.php', + 'OCP\\SystemTag\\ISystemTagObjectMapper' => $baseDir . '/lib/public/SystemTag/ISystemTagObjectMapper.php', + 'OCP\\SystemTag\\ManagerEvent' => $baseDir . '/lib/public/SystemTag/ManagerEvent.php', + 'OCP\\SystemTag\\MapperEvent' => $baseDir . '/lib/public/SystemTag/MapperEvent.php', + 'OCP\\SystemTag\\SystemTagsEntityEvent' => $baseDir . '/lib/public/SystemTag/SystemTagsEntityEvent.php', + 'OCP\\SystemTag\\TagAlreadyExistsException' => $baseDir . '/lib/public/SystemTag/TagAlreadyExistsException.php', + 'OCP\\SystemTag\\TagNotFoundException' => $baseDir . '/lib/public/SystemTag/TagNotFoundException.php', + 'OCP\\Template' => $baseDir . '/lib/public/Template.php', + 'OCP\\User' => $baseDir . '/lib/public/User.php', + 'OCP\\UserInterface' => $baseDir . '/lib/public/UserInterface.php', + 'OCP\\UserStatus\\IManager' => $baseDir . '/lib/public/UserStatus/IManager.php', + 'OCP\\UserStatus\\IProvider' => $baseDir . '/lib/public/UserStatus/IProvider.php', + 'OCP\\UserStatus\\IUserStatus' => $baseDir . '/lib/public/UserStatus/IUserStatus.php', + 'OCP\\User\\Backend\\ABackend' => $baseDir . '/lib/public/User/Backend/ABackend.php', + 'OCP\\User\\Backend\\ICheckPasswordBackend' => $baseDir . '/lib/public/User/Backend/ICheckPasswordBackend.php', + 'OCP\\User\\Backend\\ICountUsersBackend' => $baseDir . '/lib/public/User/Backend/ICountUsersBackend.php', + 'OCP\\User\\Backend\\ICreateUserBackend' => $baseDir . '/lib/public/User/Backend/ICreateUserBackend.php', + 'OCP\\User\\Backend\\ICustomLogout' => $baseDir . '/lib/public/User/Backend/ICustomLogout.php', + 'OCP\\User\\Backend\\IGetDisplayNameBackend' => $baseDir . '/lib/public/User/Backend/IGetDisplayNameBackend.php', + 'OCP\\User\\Backend\\IGetHomeBackend' => $baseDir . '/lib/public/User/Backend/IGetHomeBackend.php', + 'OCP\\User\\Backend\\IGetRealUIDBackend' => $baseDir . '/lib/public/User/Backend/IGetRealUIDBackend.php', + 'OCP\\User\\Backend\\IPasswordConfirmationBackend' => $baseDir . '/lib/public/User/Backend/IPasswordConfirmationBackend.php', + 'OCP\\User\\Backend\\IProvideAvatarBackend' => $baseDir . '/lib/public/User/Backend/IProvideAvatarBackend.php', + 'OCP\\User\\Backend\\ISetDisplayNameBackend' => $baseDir . '/lib/public/User/Backend/ISetDisplayNameBackend.php', + 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', + 'OCP\\User\\Events\\BeforePasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/BeforePasswordUpdatedEvent.php', + 'OCP\\User\\Events\\BeforeUserCreatedEvent' => $baseDir . '/lib/public/User/Events/BeforeUserCreatedEvent.php', + 'OCP\\User\\Events\\BeforeUserDeletedEvent' => $baseDir . '/lib/public/User/Events/BeforeUserDeletedEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedInEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php', + 'OCP\\User\\Events\\CreateUserEvent' => $baseDir . '/lib/public/User/Events/CreateUserEvent.php', + 'OCP\\User\\Events\\PasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/PasswordUpdatedEvent.php', + 'OCP\\User\\Events\\PostLoginEvent' => $baseDir . '/lib/public/User/Events/PostLoginEvent.php', + 'OCP\\User\\Events\\UserChangedEvent' => $baseDir . '/lib/public/User/Events/UserChangedEvent.php', + 'OCP\\User\\Events\\UserCreatedEvent' => $baseDir . '/lib/public/User/Events/UserCreatedEvent.php', + 'OCP\\User\\Events\\UserDeletedEvent' => $baseDir . '/lib/public/User/Events/UserDeletedEvent.php', + 'OCP\\User\\Events\\UserLiveStatusEvent' => $baseDir . '/lib/public/User/Events/UserLiveStatusEvent.php', + 'OCP\\User\\Events\\UserLoggedInEvent' => $baseDir . '/lib/public/User/Events/UserLoggedInEvent.php', + 'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php', + 'OCP\\User\\Events\\UserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/UserLoggedOutEvent.php', + 'OCP\\User\\GetQuotaEvent' => $baseDir . '/lib/public/User/GetQuotaEvent.php', + 'OCP\\Util' => $baseDir . '/lib/public/Util.php', + 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', + 'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', + 'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php', + 'OCP\\WorkflowEngine\\EntityContext\\IIcon' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IIcon.php', + 'OCP\\WorkflowEngine\\EntityContext\\IUrl' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IUrl.php', + 'OCP\\WorkflowEngine\\Events\\LoadSettingsScriptsEvent' => $baseDir . '/lib/public/WorkflowEngine/Events/LoadSettingsScriptsEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterChecksEvent' => $baseDir . '/lib/public/WorkflowEngine/Events/RegisterChecksEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterEntitiesEvent' => $baseDir . '/lib/public/WorkflowEngine/Events/RegisterEntitiesEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterOperationsEvent' => $baseDir . '/lib/public/WorkflowEngine/Events/RegisterOperationsEvent.php', + 'OCP\\WorkflowEngine\\GenericEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/GenericEntityEvent.php', + 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IComplexOperation' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperation.php', + 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', + 'OCP\\WorkflowEngine\\IEntityCheck' => $baseDir . '/lib/public/WorkflowEngine/IEntityCheck.php', + 'OCP\\WorkflowEngine\\IEntityCompat' => $baseDir . '/lib/public/WorkflowEngine/IEntityCompat.php', + 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', + 'OCP\\WorkflowEngine\\IFileCheck' => $baseDir . '/lib/public/WorkflowEngine/IFileCheck.php', + 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', + 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IOperationCompat' => $baseDir . '/lib/public/WorkflowEngine/IOperationCompat.php', + 'OCP\\WorkflowEngine\\IRuleMatcher' => $baseDir . '/lib/public/WorkflowEngine/IRuleMatcher.php', + 'OCP\\WorkflowEngine\\ISpecificOperation' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperation.php', + 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', + 'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php', + 'OC\\Accounts\\AccountProperty' => $baseDir . '/lib/private/Accounts/AccountProperty.php', + 'OC\\Accounts\\Hooks' => $baseDir . '/lib/private/Accounts/Hooks.php', + 'OC\\Activity\\ActivitySettingsAdapter' => $baseDir . '/lib/private/Activity/ActivitySettingsAdapter.php', + 'OC\\Activity\\Event' => $baseDir . '/lib/private/Activity/Event.php', + 'OC\\Activity\\EventMerger' => $baseDir . '/lib/private/Activity/EventMerger.php', + 'OC\\Activity\\Manager' => $baseDir . '/lib/private/Activity/Manager.php', + 'OC\\AllConfig' => $baseDir . '/lib/private/AllConfig.php', + 'OC\\AppConfig' => $baseDir . '/lib/private/AppConfig.php', + 'OC\\AppFramework\\App' => $baseDir . '/lib/private/AppFramework/App.php', + 'OC\\AppFramework\\Bootstrap\\BootContext' => $baseDir . '/lib/private/AppFramework/Bootstrap/BootContext.php', + 'OC\\AppFramework\\Bootstrap\\Coordinator' => $baseDir . '/lib/private/AppFramework/Bootstrap/Coordinator.php', + 'OC\\AppFramework\\Bootstrap\\FunctionInjector' => $baseDir . '/lib/private/AppFramework/Bootstrap/FunctionInjector.php', + 'OC\\AppFramework\\Bootstrap\\RegistrationContext' => $baseDir . '/lib/private/AppFramework/Bootstrap/RegistrationContext.php', + 'OC\\AppFramework\\DependencyInjection\\DIContainer' => $baseDir . '/lib/private/AppFramework/DependencyInjection/DIContainer.php', + 'OC\\AppFramework\\Http' => $baseDir . '/lib/private/AppFramework/Http.php', + 'OC\\AppFramework\\Http\\Dispatcher' => $baseDir . '/lib/private/AppFramework/Http/Dispatcher.php', + 'OC\\AppFramework\\Http\\Output' => $baseDir . '/lib/private/AppFramework/Http/Output.php', + 'OC\\AppFramework\\Http\\Request' => $baseDir . '/lib/private/AppFramework/Http/Request.php', + 'OC\\AppFramework\\Logger' => $baseDir . '/lib/private/AppFramework/Logger.php', + 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', + 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', + 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => $baseDir . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', + 'OC\\AppFramework\\Middleware\\NotModifiedMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php', + 'OC\\AppFramework\\Middleware\\OCSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\CSPMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\CrossSiteRequestForgeryException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\LaxSameSiteCookieFailedException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotAdminException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotConfirmedException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\ReloadExecutionException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php', + 'OC\\AppFramework\\Middleware\\Security\\FeaturePolicyMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\SecurityMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php', + 'OC\\AppFramework\\Middleware\\SessionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/SessionMiddleware.php', + 'OC\\AppFramework\\OCS\\BaseResponse' => $baseDir . '/lib/private/AppFramework/OCS/BaseResponse.php', + 'OC\\AppFramework\\OCS\\V1Response' => $baseDir . '/lib/private/AppFramework/OCS/V1Response.php', + 'OC\\AppFramework\\OCS\\V2Response' => $baseDir . '/lib/private/AppFramework/OCS/V2Response.php', + 'OC\\AppFramework\\Routing\\RouteActionHandler' => $baseDir . '/lib/private/AppFramework/Routing/RouteActionHandler.php', + 'OC\\AppFramework\\Routing\\RouteConfig' => $baseDir . '/lib/private/AppFramework/Routing/RouteConfig.php', + 'OC\\AppFramework\\ScopedPsrLogger' => $baseDir . '/lib/private/AppFramework/ScopedPsrLogger.php', + 'OC\\AppFramework\\Services\\AppConfig' => $baseDir . '/lib/private/AppFramework/Services/AppConfig.php', + 'OC\\AppFramework\\Services\\InitialState' => $baseDir . '/lib/private/AppFramework/Services/InitialState.php', + 'OC\\AppFramework\\Utility\\ControllerMethodReflector' => $baseDir . '/lib/private/AppFramework/Utility/ControllerMethodReflector.php', + 'OC\\AppFramework\\Utility\\SimpleContainer' => $baseDir . '/lib/private/AppFramework/Utility/SimpleContainer.php', + 'OC\\AppFramework\\Utility\\TimeFactory' => $baseDir . '/lib/private/AppFramework/Utility/TimeFactory.php', + 'OC\\App\\AppManager' => $baseDir . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Bundles\\Bundle' => $baseDir . '/lib/private/App/AppStore/Bundles/Bundle.php', + 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => $baseDir . '/lib/private/App/AppStore/Bundles/BundleFetcher.php', + 'OC\\App\\AppStore\\Bundles\\CoreBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/CoreBundle.php', + 'OC\\App\\AppStore\\Bundles\\EducationBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/EducationBundle.php', + 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php', + 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php', + 'OC\\App\\AppStore\\Bundles\\HubBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/HubBundle.php', + 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php', + 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\Fetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/Fetcher.php', + 'OC\\App\\AppStore\\Version\\Version' => $baseDir . '/lib/private/App/AppStore/Version/Version.php', + 'OC\\App\\AppStore\\Version\\VersionParser' => $baseDir . '/lib/private/App/AppStore/Version/VersionParser.php', + 'OC\\App\\CodeChecker\\AbstractCheck' => $baseDir . '/lib/private/App/CodeChecker/AbstractCheck.php', + 'OC\\App\\CodeChecker\\CodeChecker' => $baseDir . '/lib/private/App/CodeChecker/CodeChecker.php', + 'OC\\App\\CodeChecker\\DatabaseSchemaChecker' => $baseDir . '/lib/private/App/CodeChecker/DatabaseSchemaChecker.php', + 'OC\\App\\CodeChecker\\DeprecationCheck' => $baseDir . '/lib/private/App/CodeChecker/DeprecationCheck.php', + 'OC\\App\\CodeChecker\\EmptyCheck' => $baseDir . '/lib/private/App/CodeChecker/EmptyCheck.php', + 'OC\\App\\CodeChecker\\ICheck' => $baseDir . '/lib/private/App/CodeChecker/ICheck.php', + 'OC\\App\\CodeChecker\\InfoChecker' => $baseDir . '/lib/private/App/CodeChecker/InfoChecker.php', + 'OC\\App\\CodeChecker\\LanguageParseChecker' => $baseDir . '/lib/private/App/CodeChecker/LanguageParseChecker.php', + 'OC\\App\\CodeChecker\\MigrationSchemaChecker' => $baseDir . '/lib/private/App/CodeChecker/MigrationSchemaChecker.php', + 'OC\\App\\CodeChecker\\NodeVisitor' => $baseDir . '/lib/private/App/CodeChecker/NodeVisitor.php', + 'OC\\App\\CodeChecker\\PrivateCheck' => $baseDir . '/lib/private/App/CodeChecker/PrivateCheck.php', + 'OC\\App\\CodeChecker\\StrongComparisonCheck' => $baseDir . '/lib/private/App/CodeChecker/StrongComparisonCheck.php', + 'OC\\App\\CompareVersion' => $baseDir . '/lib/private/App/CompareVersion.php', + 'OC\\App\\DependencyAnalyzer' => $baseDir . '/lib/private/App/DependencyAnalyzer.php', + 'OC\\App\\InfoParser' => $baseDir . '/lib/private/App/InfoParser.php', + 'OC\\App\\Platform' => $baseDir . '/lib/private/App/Platform.php', + 'OC\\App\\PlatformRepository' => $baseDir . '/lib/private/App/PlatformRepository.php', + 'OC\\Archive\\Archive' => $baseDir . '/lib/private/Archive/Archive.php', + 'OC\\Archive\\TAR' => $baseDir . '/lib/private/Archive/TAR.php', + 'OC\\Archive\\ZIP' => $baseDir . '/lib/private/Archive/ZIP.php', + 'OC\\Authentication\\Events\\ARemoteWipeEvent' => $baseDir . '/lib/private/Authentication/Events/ARemoteWipeEvent.php', + 'OC\\Authentication\\Events\\LoginFailed' => $baseDir . '/lib/private/Authentication/Events/LoginFailed.php', + 'OC\\Authentication\\Events\\RemoteWipeFinished' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeFinished.php', + 'OC\\Authentication\\Events\\RemoteWipeStarted' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeStarted.php', + 'OC\\Authentication\\Exceptions\\ExpiredTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/ExpiredTokenException.php', + 'OC\\Authentication\\Exceptions\\InvalidProviderException' => $baseDir . '/lib/private/Authentication/Exceptions/InvalidProviderException.php', + 'OC\\Authentication\\Exceptions\\InvalidTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/InvalidTokenException.php', + 'OC\\Authentication\\Exceptions\\LoginRequiredException' => $baseDir . '/lib/private/Authentication/Exceptions/LoginRequiredException.php', + 'OC\\Authentication\\Exceptions\\PasswordLoginForbiddenException' => $baseDir . '/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php', + 'OC\\Authentication\\Exceptions\\PasswordlessTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/PasswordlessTokenException.php', + 'OC\\Authentication\\Exceptions\\TokenPasswordExpiredException' => $baseDir . '/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php', + 'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => $baseDir . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php', + 'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => $baseDir . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php', + 'OC\\Authentication\\Exceptions\\WipeTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/WipeTokenException.php', + 'OC\\Authentication\\Listeners\\LoginFailedListener' => $baseDir . '/lib/private/Authentication/Listeners/LoginFailedListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeActivityListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php', + 'OC\\Authentication\\Listeners\\UserDeletedStoreCleanupListener' => $baseDir . '/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php', + 'OC\\Authentication\\Listeners\\UserDeletedTokenCleanupListener' => $baseDir . '/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php', + 'OC\\Authentication\\Listeners\\UserLoggedInListener' => $baseDir . '/lib/private/Authentication/Listeners/UserLoggedInListener.php', + 'OC\\Authentication\\LoginCredentials\\Credentials' => $baseDir . '/lib/private/Authentication/LoginCredentials/Credentials.php', + 'OC\\Authentication\\LoginCredentials\\Store' => $baseDir . '/lib/private/Authentication/LoginCredentials/Store.php', + 'OC\\Authentication\\Login\\ALoginCommand' => $baseDir . '/lib/private/Authentication/Login/ALoginCommand.php', + 'OC\\Authentication\\Login\\Chain' => $baseDir . '/lib/private/Authentication/Login/Chain.php', + 'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => $baseDir . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php', + 'OC\\Authentication\\Login\\CompleteLoginCommand' => $baseDir . '/lib/private/Authentication/Login/CompleteLoginCommand.php', + 'OC\\Authentication\\Login\\CreateSessionTokenCommand' => $baseDir . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php', + 'OC\\Authentication\\Login\\EmailLoginCommand' => $baseDir . '/lib/private/Authentication/Login/EmailLoginCommand.php', + 'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => $baseDir . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php', + 'OC\\Authentication\\Login\\LoggedInCheckCommand' => $baseDir . '/lib/private/Authentication/Login/LoggedInCheckCommand.php', + 'OC\\Authentication\\Login\\LoginData' => $baseDir . '/lib/private/Authentication/Login/LoginData.php', + 'OC\\Authentication\\Login\\LoginResult' => $baseDir . '/lib/private/Authentication/Login/LoginResult.php', + 'OC\\Authentication\\Login\\PreLoginHookCommand' => $baseDir . '/lib/private/Authentication/Login/PreLoginHookCommand.php', + 'OC\\Authentication\\Login\\SetUserTimezoneCommand' => $baseDir . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php', + 'OC\\Authentication\\Login\\TwoFactorCommand' => $baseDir . '/lib/private/Authentication/Login/TwoFactorCommand.php', + 'OC\\Authentication\\Login\\UidLoginCommand' => $baseDir . '/lib/private/Authentication/Login/UidLoginCommand.php', + 'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => $baseDir . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php', + 'OC\\Authentication\\Login\\UserDisabledCheckCommand' => $baseDir . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php', + 'OC\\Authentication\\Login\\WebAuthnChain' => $baseDir . '/lib/private/Authentication/Login/WebAuthnChain.php', + 'OC\\Authentication\\Login\\WebAuthnLoginCommand' => $baseDir . '/lib/private/Authentication/Login/WebAuthnLoginCommand.php', + 'OC\\Authentication\\Notifications\\Notifier' => $baseDir . '/lib/private/Authentication/Notifications/Notifier.php', + 'OC\\Authentication\\Token\\DefaultToken' => $baseDir . '/lib/private/Authentication/Token/DefaultToken.php', + 'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php', + 'OC\\Authentication\\Token\\DefaultTokenMapper' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenMapper.php', + 'OC\\Authentication\\Token\\DefaultTokenProvider' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenProvider.php', + 'OC\\Authentication\\Token\\INamedToken' => $baseDir . '/lib/private/Authentication/Token/INamedToken.php', + 'OC\\Authentication\\Token\\IProvider' => $baseDir . '/lib/private/Authentication/Token/IProvider.php', + 'OC\\Authentication\\Token\\IToken' => $baseDir . '/lib/private/Authentication/Token/IToken.php', + 'OC\\Authentication\\Token\\IWipeableToken' => $baseDir . '/lib/private/Authentication/Token/IWipeableToken.php', + 'OC\\Authentication\\Token\\Manager' => $baseDir . '/lib/private/Authentication/Token/Manager.php', + 'OC\\Authentication\\Token\\PublicKeyToken' => $baseDir . '/lib/private/Authentication/Token/PublicKeyToken.php', + 'OC\\Authentication\\Token\\PublicKeyTokenMapper' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php', + 'OC\\Authentication\\Token\\PublicKeyTokenProvider' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php', + 'OC\\Authentication\\Token\\RemoteWipe' => $baseDir . '/lib/private/Authentication/Token/RemoteWipe.php', + 'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php', + 'OC\\Authentication\\TwoFactorAuth\\EnforcementState' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php', + 'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php', + 'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', + 'OC\\Authentication\\TwoFactorAuth\\Registry' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Registry.php', + 'OC\\Authentication\\WebAuthn\\CredentialRepository' => $baseDir . '/lib/private/Authentication/WebAuthn/CredentialRepository.php', + 'OC\\Authentication\\WebAuthn\\Db\\PublicKeyCredentialEntity' => $baseDir . '/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php', + 'OC\\Authentication\\WebAuthn\\Db\\PublicKeyCredentialMapper' => $baseDir . '/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php', + 'OC\\Authentication\\WebAuthn\\Manager' => $baseDir . '/lib/private/Authentication/WebAuthn/Manager.php', + 'OC\\Avatar\\Avatar' => $baseDir . '/lib/private/Avatar/Avatar.php', + 'OC\\Avatar\\AvatarManager' => $baseDir . '/lib/private/Avatar/AvatarManager.php', + 'OC\\Avatar\\GuestAvatar' => $baseDir . '/lib/private/Avatar/GuestAvatar.php', + 'OC\\Avatar\\UserAvatar' => $baseDir . '/lib/private/Avatar/UserAvatar.php', + 'OC\\BackgroundJob\\Job' => $baseDir . '/lib/private/BackgroundJob/Job.php', + 'OC\\BackgroundJob\\JobList' => $baseDir . '/lib/private/BackgroundJob/JobList.php', + 'OC\\BackgroundJob\\Legacy\\QueuedJob' => $baseDir . '/lib/private/BackgroundJob/Legacy/QueuedJob.php', + 'OC\\BackgroundJob\\Legacy\\RegularJob' => $baseDir . '/lib/private/BackgroundJob/Legacy/RegularJob.php', + 'OC\\BackgroundJob\\QueuedJob' => $baseDir . '/lib/private/BackgroundJob/QueuedJob.php', + 'OC\\BackgroundJob\\TimedJob' => $baseDir . '/lib/private/BackgroundJob/TimedJob.php', + 'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php', + 'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php', + 'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php', + 'OC\\Calendar\\Manager' => $baseDir . '/lib/private/Calendar/Manager.php', + 'OC\\Calendar\\Resource\\Manager' => $baseDir . '/lib/private/Calendar/Resource/Manager.php', + 'OC\\Calendar\\Room\\Manager' => $baseDir . '/lib/private/Calendar/Room/Manager.php', + 'OC\\CapabilitiesManager' => $baseDir . '/lib/private/CapabilitiesManager.php', + 'OC\\Collaboration\\AutoComplete\\Manager' => $baseDir . '/lib/private/Collaboration/AutoComplete/Manager.php', + 'OC\\Collaboration\\Collaborators\\GroupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/GroupPlugin.php', + 'OC\\Collaboration\\Collaborators\\LookupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/LookupPlugin.php', + 'OC\\Collaboration\\Collaborators\\MailPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/MailPlugin.php', + 'OC\\Collaboration\\Collaborators\\RemoteGroupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php', + 'OC\\Collaboration\\Collaborators\\RemotePlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/RemotePlugin.php', + 'OC\\Collaboration\\Collaborators\\Search' => $baseDir . '/lib/private/Collaboration/Collaborators/Search.php', + 'OC\\Collaboration\\Collaborators\\SearchResult' => $baseDir . '/lib/private/Collaboration/Collaborators/SearchResult.php', + 'OC\\Collaboration\\Collaborators\\UserPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserPlugin.php', + 'OC\\Collaboration\\Resources\\Collection' => $baseDir . '/lib/private/Collaboration/Resources/Collection.php', + 'OC\\Collaboration\\Resources\\Listener' => $baseDir . '/lib/private/Collaboration/Resources/Listener.php', + 'OC\\Collaboration\\Resources\\Manager' => $baseDir . '/lib/private/Collaboration/Resources/Manager.php', + 'OC\\Collaboration\\Resources\\ProviderManager' => $baseDir . '/lib/private/Collaboration/Resources/ProviderManager.php', + 'OC\\Collaboration\\Resources\\Resource' => $baseDir . '/lib/private/Collaboration/Resources/Resource.php', + 'OC\\Color' => $baseDir . '/lib/private/Color.php', + 'OC\\Command\\AsyncBus' => $baseDir . '/lib/private/Command/AsyncBus.php', + 'OC\\Command\\CallableJob' => $baseDir . '/lib/private/Command/CallableJob.php', + 'OC\\Command\\ClosureJob' => $baseDir . '/lib/private/Command/ClosureJob.php', + 'OC\\Command\\CommandJob' => $baseDir . '/lib/private/Command/CommandJob.php', + 'OC\\Command\\CronBus' => $baseDir . '/lib/private/Command/CronBus.php', + 'OC\\Command\\FileAccess' => $baseDir . '/lib/private/Command/FileAccess.php', + 'OC\\Command\\QueueBus' => $baseDir . '/lib/private/Command/QueueBus.php', + 'OC\\Comments\\Comment' => $baseDir . '/lib/private/Comments/Comment.php', + 'OC\\Comments\\Manager' => $baseDir . '/lib/private/Comments/Manager.php', + 'OC\\Comments\\ManagerFactory' => $baseDir . '/lib/private/Comments/ManagerFactory.php', + 'OC\\Config' => $baseDir . '/lib/private/Config.php', + 'OC\\Console\\Application' => $baseDir . '/lib/private/Console/Application.php', + 'OC\\Console\\TimestampFormatter' => $baseDir . '/lib/private/Console/TimestampFormatter.php', + 'OC\\ContactsManager' => $baseDir . '/lib/private/ContactsManager.php', + 'OC\\Contacts\\ContactsMenu\\ActionFactory' => $baseDir . '/lib/private/Contacts/ContactsMenu/ActionFactory.php', + 'OC\\Contacts\\ContactsMenu\\ActionProviderStore' => $baseDir . '/lib/private/Contacts/ContactsMenu/ActionProviderStore.php', + 'OC\\Contacts\\ContactsMenu\\Actions\\LinkAction' => $baseDir . '/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php', + 'OC\\Contacts\\ContactsMenu\\ContactsStore' => $baseDir . '/lib/private/Contacts/ContactsMenu/ContactsStore.php', + 'OC\\Contacts\\ContactsMenu\\Entry' => $baseDir . '/lib/private/Contacts/ContactsMenu/Entry.php', + 'OC\\Contacts\\ContactsMenu\\Manager' => $baseDir . '/lib/private/Contacts/ContactsMenu/Manager.php', + 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', + 'OC\\Core\\Application' => $baseDir . '/core/Application.php', + 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', + 'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => $baseDir . '/core/BackgroundJobs/CleanupLoginFlowV2.php', + 'OC\\Core\\Command\\App\\CheckCode' => $baseDir . '/core/Command/App/CheckCode.php', + 'OC\\Core\\Command\\App\\Disable' => $baseDir . '/core/Command/App/Disable.php', + 'OC\\Core\\Command\\App\\Enable' => $baseDir . '/core/Command/App/Enable.php', + 'OC\\Core\\Command\\App\\GetPath' => $baseDir . '/core/Command/App/GetPath.php', + 'OC\\Core\\Command\\App\\Install' => $baseDir . '/core/Command/App/Install.php', + 'OC\\Core\\Command\\App\\ListApps' => $baseDir . '/core/Command/App/ListApps.php', + 'OC\\Core\\Command\\App\\Remove' => $baseDir . '/core/Command/App/Remove.php', + 'OC\\Core\\Command\\App\\Update' => $baseDir . '/core/Command/App/Update.php', + 'OC\\Core\\Command\\Background\\Ajax' => $baseDir . '/core/Command/Background/Ajax.php', + 'OC\\Core\\Command\\Background\\Base' => $baseDir . '/core/Command/Background/Base.php', + 'OC\\Core\\Command\\Background\\Cron' => $baseDir . '/core/Command/Background/Cron.php', + 'OC\\Core\\Command\\Background\\WebCron' => $baseDir . '/core/Command/Background/WebCron.php', + 'OC\\Core\\Command\\Base' => $baseDir . '/core/Command/Base.php', + 'OC\\Core\\Command\\Broadcast\\Test' => $baseDir . '/core/Command/Broadcast/Test.php', + 'OC\\Core\\Command\\Check' => $baseDir . '/core/Command/Check.php', + 'OC\\Core\\Command\\Config\\App\\Base' => $baseDir . '/core/Command/Config/App/Base.php', + 'OC\\Core\\Command\\Config\\App\\DeleteConfig' => $baseDir . '/core/Command/Config/App/DeleteConfig.php', + 'OC\\Core\\Command\\Config\\App\\GetConfig' => $baseDir . '/core/Command/Config/App/GetConfig.php', + 'OC\\Core\\Command\\Config\\App\\SetConfig' => $baseDir . '/core/Command/Config/App/SetConfig.php', + 'OC\\Core\\Command\\Config\\Import' => $baseDir . '/core/Command/Config/Import.php', + 'OC\\Core\\Command\\Config\\ListConfigs' => $baseDir . '/core/Command/Config/ListConfigs.php', + 'OC\\Core\\Command\\Config\\System\\Base' => $baseDir . '/core/Command/Config/System/Base.php', + 'OC\\Core\\Command\\Config\\System\\DeleteConfig' => $baseDir . '/core/Command/Config/System/DeleteConfig.php', + 'OC\\Core\\Command\\Config\\System\\GetConfig' => $baseDir . '/core/Command/Config/System/GetConfig.php', + 'OC\\Core\\Command\\Config\\System\\SetConfig' => $baseDir . '/core/Command/Config/System/SetConfig.php', + 'OC\\Core\\Command\\Db\\AddMissingColumns' => $baseDir . '/core/Command/Db/AddMissingColumns.php', + 'OC\\Core\\Command\\Db\\AddMissingIndices' => $baseDir . '/core/Command/Db/AddMissingIndices.php', + 'OC\\Core\\Command\\Db\\ConvertFilecacheBigInt' => $baseDir . '/core/Command/Db/ConvertFilecacheBigInt.php', + 'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => $baseDir . '/core/Command/Db/ConvertMysqlToMB4.php', + 'OC\\Core\\Command\\Db\\ConvertType' => $baseDir . '/core/Command/Db/ConvertType.php', + 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => $baseDir . '/core/Command/Db/Migrations/ExecuteCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => $baseDir . '/core/Command/Db/Migrations/GenerateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateFromSchemaFileCommand' => $baseDir . '/core/Command/Db/Migrations/GenerateFromSchemaFileCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => $baseDir . '/core/Command/Db/Migrations/MigrateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => $baseDir . '/core/Command/Db/Migrations/StatusCommand.php', + 'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ChangeKeyStorageRoot.php', + 'OC\\Core\\Command\\Encryption\\DecryptAll' => $baseDir . '/core/Command/Encryption/DecryptAll.php', + 'OC\\Core\\Command\\Encryption\\Disable' => $baseDir . '/core/Command/Encryption/Disable.php', + 'OC\\Core\\Command\\Encryption\\Enable' => $baseDir . '/core/Command/Encryption/Enable.php', + 'OC\\Core\\Command\\Encryption\\EncryptAll' => $baseDir . '/core/Command/Encryption/EncryptAll.php', + 'OC\\Core\\Command\\Encryption\\ListModules' => $baseDir . '/core/Command/Encryption/ListModules.php', + 'OC\\Core\\Command\\Encryption\\MigrateKeyStorage' => $baseDir . '/core/Command/Encryption/MigrateKeyStorage.php', + 'OC\\Core\\Command\\Encryption\\SetDefaultModule' => $baseDir . '/core/Command/Encryption/SetDefaultModule.php', + 'OC\\Core\\Command\\Encryption\\ShowKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ShowKeyStorageRoot.php', + 'OC\\Core\\Command\\Encryption\\Status' => $baseDir . '/core/Command/Encryption/Status.php', + 'OC\\Core\\Command\\Group\\Add' => $baseDir . '/core/Command/Group/Add.php', + 'OC\\Core\\Command\\Group\\AddUser' => $baseDir . '/core/Command/Group/AddUser.php', + 'OC\\Core\\Command\\Group\\Delete' => $baseDir . '/core/Command/Group/Delete.php', + 'OC\\Core\\Command\\Group\\ListCommand' => $baseDir . '/core/Command/Group/ListCommand.php', + 'OC\\Core\\Command\\Group\\RemoveUser' => $baseDir . '/core/Command/Group/RemoveUser.php', + 'OC\\Core\\Command\\Integrity\\CheckApp' => $baseDir . '/core/Command/Integrity/CheckApp.php', + 'OC\\Core\\Command\\Integrity\\CheckCore' => $baseDir . '/core/Command/Integrity/CheckCore.php', + 'OC\\Core\\Command\\Integrity\\SignApp' => $baseDir . '/core/Command/Integrity/SignApp.php', + 'OC\\Core\\Command\\Integrity\\SignCore' => $baseDir . '/core/Command/Integrity/SignCore.php', + 'OC\\Core\\Command\\InterruptedException' => $baseDir . '/core/Command/InterruptedException.php', + 'OC\\Core\\Command\\L10n\\CreateJs' => $baseDir . '/core/Command/L10n/CreateJs.php', + 'OC\\Core\\Command\\Log\\File' => $baseDir . '/core/Command/Log/File.php', + 'OC\\Core\\Command\\Log\\Manage' => $baseDir . '/core/Command/Log/Manage.php', + 'OC\\Core\\Command\\Maintenance\\DataFingerprint' => $baseDir . '/core/Command/Maintenance/DataFingerprint.php', + 'OC\\Core\\Command\\Maintenance\\Install' => $baseDir . '/core/Command/Maintenance/Install.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\GenerateMimetypeFileBuilder' => $baseDir . '/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateDB' => $baseDir . '/core/Command/Maintenance/Mimetype/UpdateDB.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => $baseDir . '/core/Command/Maintenance/Mimetype/UpdateJS.php', + 'OC\\Core\\Command\\Maintenance\\Mode' => $baseDir . '/core/Command/Maintenance/Mode.php', + 'OC\\Core\\Command\\Maintenance\\Repair' => $baseDir . '/core/Command/Maintenance/Repair.php', + 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php', + 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php', + 'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php', + 'OC\\Core\\Command\\Security\\ImportCertificate' => $baseDir . '/core/Command/Security/ImportCertificate.php', + 'OC\\Core\\Command\\Security\\ListCertificates' => $baseDir . '/core/Command/Security/ListCertificates.php', + 'OC\\Core\\Command\\Security\\RemoveCertificate' => $baseDir . '/core/Command/Security/RemoveCertificate.php', + 'OC\\Core\\Command\\Security\\ResetBruteforceAttempts' => $baseDir . '/core/Command/Security/ResetBruteforceAttempts.php', + 'OC\\Core\\Command\\Status' => $baseDir . '/core/Command/Status.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Base' => $baseDir . '/core/Command/TwoFactorAuth/Base.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => $baseDir . '/core/Command/TwoFactorAuth/Cleanup.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => $baseDir . '/core/Command/TwoFactorAuth/Disable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enable' => $baseDir . '/core/Command/TwoFactorAuth/Enable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enforce' => $baseDir . '/core/Command/TwoFactorAuth/Enforce.php', + 'OC\\Core\\Command\\TwoFactorAuth\\State' => $baseDir . '/core/Command/TwoFactorAuth/State.php', + 'OC\\Core\\Command\\Upgrade' => $baseDir . '/core/Command/Upgrade.php', + 'OC\\Core\\Command\\User\\Add' => $baseDir . '/core/Command/User/Add.php', + 'OC\\Core\\Command\\User\\Delete' => $baseDir . '/core/Command/User/Delete.php', + 'OC\\Core\\Command\\User\\Disable' => $baseDir . '/core/Command/User/Disable.php', + 'OC\\Core\\Command\\User\\Enable' => $baseDir . '/core/Command/User/Enable.php', + 'OC\\Core\\Command\\User\\Info' => $baseDir . '/core/Command/User/Info.php', + 'OC\\Core\\Command\\User\\LastSeen' => $baseDir . '/core/Command/User/LastSeen.php', + 'OC\\Core\\Command\\User\\ListCommand' => $baseDir . '/core/Command/User/ListCommand.php', + 'OC\\Core\\Command\\User\\Report' => $baseDir . '/core/Command/User/Report.php', + 'OC\\Core\\Command\\User\\ResetPassword' => $baseDir . '/core/Command/User/ResetPassword.php', + 'OC\\Core\\Command\\User\\Setting' => $baseDir . '/core/Command/User/Setting.php', + 'OC\\Core\\Controller\\AppPasswordController' => $baseDir . '/core/Controller/AppPasswordController.php', + 'OC\\Core\\Controller\\AutoCompleteController' => $baseDir . '/core/Controller/AutoCompleteController.php', + 'OC\\Core\\Controller\\AvatarController' => $baseDir . '/core/Controller/AvatarController.php', + 'OC\\Core\\Controller\\CSRFTokenController' => $baseDir . '/core/Controller/CSRFTokenController.php', + 'OC\\Core\\Controller\\ClientFlowLoginController' => $baseDir . '/core/Controller/ClientFlowLoginController.php', + 'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => $baseDir . '/core/Controller/ClientFlowLoginV2Controller.php', + 'OC\\Core\\Controller\\CollaborationResourcesController' => $baseDir . '/core/Controller/CollaborationResourcesController.php', + 'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php', + 'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php', + 'OC\\Core\\Controller\\GuestAvatarController' => $baseDir . '/core/Controller/GuestAvatarController.php', + 'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php', + 'OC\\Core\\Controller\\LoginController' => $baseDir . '/core/Controller/LoginController.php', + 'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php', + 'OC\\Core\\Controller\\NavigationController' => $baseDir . '/core/Controller/NavigationController.php', + 'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php', + 'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php', + 'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php', + 'OC\\Core\\Controller\\RecommendedAppsController' => $baseDir . '/core/Controller/RecommendedAppsController.php', + 'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php', + 'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php', + 'OC\\Core\\Controller\\SvgController' => $baseDir . '/core/Controller/SvgController.php', + 'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php', + 'OC\\Core\\Controller\\UnifiedSearchController' => $baseDir . '/core/Controller/UnifiedSearchController.php', + 'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php', + 'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php', + 'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php', + 'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php', + 'OC\\Core\\Controller\\WipeController' => $baseDir . '/core/Controller/WipeController.php', + 'OC\\Core\\Data\\LoginFlowV2Credentials' => $baseDir . '/core/Data/LoginFlowV2Credentials.php', + 'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php', + 'OC\\Core\\Db\\LoginFlowV2' => $baseDir . '/core/Db/LoginFlowV2.php', + 'OC\\Core\\Db\\LoginFlowV2Mapper' => $baseDir . '/core/Db/LoginFlowV2Mapper.php', + 'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => $baseDir . '/core/Exception/LoginFlowV2NotFoundException.php', + 'OC\\Core\\Exception\\ResetPasswordException' => $baseDir . '/core/Exception/ResetPasswordException.php', + 'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php', + 'OC\\Core\\Migrations\\Version13000Date20170705121758' => $baseDir . '/core/Migrations/Version13000Date20170705121758.php', + 'OC\\Core\\Migrations\\Version13000Date20170718121200' => $baseDir . '/core/Migrations/Version13000Date20170718121200.php', + 'OC\\Core\\Migrations\\Version13000Date20170814074715' => $baseDir . '/core/Migrations/Version13000Date20170814074715.php', + 'OC\\Core\\Migrations\\Version13000Date20170919121250' => $baseDir . '/core/Migrations/Version13000Date20170919121250.php', + 'OC\\Core\\Migrations\\Version13000Date20170926101637' => $baseDir . '/core/Migrations/Version13000Date20170926101637.php', + 'OC\\Core\\Migrations\\Version14000Date20180129121024' => $baseDir . '/core/Migrations/Version14000Date20180129121024.php', + 'OC\\Core\\Migrations\\Version14000Date20180404140050' => $baseDir . '/core/Migrations/Version14000Date20180404140050.php', + 'OC\\Core\\Migrations\\Version14000Date20180516101403' => $baseDir . '/core/Migrations/Version14000Date20180516101403.php', + 'OC\\Core\\Migrations\\Version14000Date20180518120534' => $baseDir . '/core/Migrations/Version14000Date20180518120534.php', + 'OC\\Core\\Migrations\\Version14000Date20180522074438' => $baseDir . '/core/Migrations/Version14000Date20180522074438.php', + 'OC\\Core\\Migrations\\Version14000Date20180626223656' => $baseDir . '/core/Migrations/Version14000Date20180626223656.php', + 'OC\\Core\\Migrations\\Version14000Date20180710092004' => $baseDir . '/core/Migrations/Version14000Date20180710092004.php', + 'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.php', + 'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php', + 'OC\\Core\\Migrations\\Version15000Date20181015062942' => $baseDir . '/core/Migrations/Version15000Date20181015062942.php', + 'OC\\Core\\Migrations\\Version15000Date20181029084625' => $baseDir . '/core/Migrations/Version15000Date20181029084625.php', + 'OC\\Core\\Migrations\\Version16000Date20190207141427' => $baseDir . '/core/Migrations/Version16000Date20190207141427.php', + 'OC\\Core\\Migrations\\Version16000Date20190212081545' => $baseDir . '/core/Migrations/Version16000Date20190212081545.php', + 'OC\\Core\\Migrations\\Version16000Date20190427105638' => $baseDir . '/core/Migrations/Version16000Date20190427105638.php', + 'OC\\Core\\Migrations\\Version16000Date20190428150708' => $baseDir . '/core/Migrations/Version16000Date20190428150708.php', + 'OC\\Core\\Migrations\\Version17000Date20190514105811' => $baseDir . '/core/Migrations/Version17000Date20190514105811.php', + 'OC\\Core\\Migrations\\Version18000Date20190920085628' => $baseDir . '/core/Migrations/Version18000Date20190920085628.php', + 'OC\\Core\\Migrations\\Version18000Date20191014105105' => $baseDir . '/core/Migrations/Version18000Date20191014105105.php', + 'OC\\Core\\Migrations\\Version18000Date20191204114856' => $baseDir . '/core/Migrations/Version18000Date20191204114856.php', + 'OC\\Core\\Migrations\\Version19000Date20200211083441' => $baseDir . '/core/Migrations/Version19000Date20200211083441.php', + 'OC\\Core\\Notification\\RemoveLinkSharesNotifier' => $baseDir . '/core/Notification/RemoveLinkSharesNotifier.php', + 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', + 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', + 'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php', + 'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', + 'OC\\DB\\AdapterPgSql' => $baseDir . '/lib/private/DB/AdapterPgSql.php', + 'OC\\DB\\AdapterSqlite' => $baseDir . '/lib/private/DB/AdapterSqlite.php', + 'OC\\DB\\Connection' => $baseDir . '/lib/private/DB/Connection.php', + 'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.php', + 'OC\\DB\\MDB2SchemaManager' => $baseDir . '/lib/private/DB/MDB2SchemaManager.php', + 'OC\\DB\\MDB2SchemaReader' => $baseDir . '/lib/private/DB/MDB2SchemaReader.php', + 'OC\\DB\\MDB2SchemaWriter' => $baseDir . '/lib/private/DB/MDB2SchemaWriter.php', + 'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.php', + 'OC\\DB\\MigrationService' => $baseDir . '/lib/private/DB/MigrationService.php', + 'OC\\DB\\Migrator' => $baseDir . '/lib/private/DB/Migrator.php', + 'OC\\DB\\MissingColumnInformation' => $baseDir . '/lib/private/DB/MissingColumnInformation.php', + 'OC\\DB\\MissingIndexInformation' => $baseDir . '/lib/private/DB/MissingIndexInformation.php', + 'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php', + 'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php', + 'OC\\DB\\OCSqlitePlatform' => $baseDir . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\OracleConnection' => $baseDir . '/lib/private/DB/OracleConnection.php', + 'OC\\DB\\OracleMigrator' => $baseDir . '/lib/private/DB/OracleMigrator.php', + 'OC\\DB\\PgSqlTools' => $baseDir . '/lib/private/DB/PgSqlTools.php', + 'OC\\DB\\PostgreSqlMigrator' => $baseDir . '/lib/private/DB/PostgreSqlMigrator.php', + 'OC\\DB\\QueryBuilder\\CompositeExpression' => $baseDir . '/lib/private/DB/QueryBuilder/CompositeExpression.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\ExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\MySqlExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\OCIExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\PgSqlExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\SqliteExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\FunctionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\OCIFunctionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\PgSqlFunctionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\SqliteFunctionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\Literal' => $baseDir . '/lib/private/DB/QueryBuilder/Literal.php', + 'OC\\DB\\QueryBuilder\\Parameter' => $baseDir . '/lib/private/DB/QueryBuilder/Parameter.php', + 'OC\\DB\\QueryBuilder\\QueryBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/QueryBuilder.php', + 'OC\\DB\\QueryBuilder\\QueryFunction' => $baseDir . '/lib/private/DB/QueryBuilder/QueryFunction.php', + 'OC\\DB\\QueryBuilder\\QuoteHelper' => $baseDir . '/lib/private/DB/QueryBuilder/QuoteHelper.php', + 'OC\\DB\\ReconnectWrapper' => $baseDir . '/lib/private/DB/ReconnectWrapper.php', + 'OC\\DB\\SQLiteMigrator' => $baseDir . '/lib/private/DB/SQLiteMigrator.php', + 'OC\\DB\\SQLiteSessionInit' => $baseDir . '/lib/private/DB/SQLiteSessionInit.php', + 'OC\\DB\\SchemaWrapper' => $baseDir . '/lib/private/DB/SchemaWrapper.php', + 'OC\\DB\\SetTransactionIsolationLevel' => $baseDir . '/lib/private/DB/SetTransactionIsolationLevel.php', + 'OC\\Dashboard\\DashboardManager' => $baseDir . '/lib/private/Dashboard/DashboardManager.php', + 'OC\\Dashboard\\Manager' => $baseDir . '/lib/private/Dashboard/Manager.php', + 'OC\\DatabaseException' => $baseDir . '/lib/private/DatabaseException.php', + 'OC\\DatabaseSetupException' => $baseDir . '/lib/private/DatabaseSetupException.php', + 'OC\\DateTimeFormatter' => $baseDir . '/lib/private/DateTimeFormatter.php', + 'OC\\DateTimeZone' => $baseDir . '/lib/private/DateTimeZone.php', + 'OC\\Diagnostics\\Event' => $baseDir . '/lib/private/Diagnostics/Event.php', + 'OC\\Diagnostics\\EventLogger' => $baseDir . '/lib/private/Diagnostics/EventLogger.php', + 'OC\\Diagnostics\\Query' => $baseDir . '/lib/private/Diagnostics/Query.php', + 'OC\\Diagnostics\\QueryLogger' => $baseDir . '/lib/private/Diagnostics/QueryLogger.php', + 'OC\\DirectEditing\\Manager' => $baseDir . '/lib/private/DirectEditing/Manager.php', + 'OC\\DirectEditing\\Token' => $baseDir . '/lib/private/DirectEditing/Token.php', + 'OC\\Encryption\\DecryptAll' => $baseDir . '/lib/private/Encryption/DecryptAll.php', + 'OC\\Encryption\\EncryptionWrapper' => $baseDir . '/lib/private/Encryption/EncryptionWrapper.php', + 'OC\\Encryption\\Exceptions\\DecryptionFailedException' => $baseDir . '/lib/private/Encryption/Exceptions/DecryptionFailedException.php', + 'OC\\Encryption\\Exceptions\\EmptyEncryptionDataException' => $baseDir . '/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php', + 'OC\\Encryption\\Exceptions\\EncryptionFailedException' => $baseDir . '/lib/private/Encryption/Exceptions/EncryptionFailedException.php', + 'OC\\Encryption\\Exceptions\\EncryptionHeaderKeyExistsException' => $baseDir . '/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php', + 'OC\\Encryption\\Exceptions\\EncryptionHeaderToLargeException' => $baseDir . '/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php', + 'OC\\Encryption\\Exceptions\\ModuleAlreadyExistsException' => $baseDir . '/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php', + 'OC\\Encryption\\Exceptions\\ModuleDoesNotExistsException' => $baseDir . '/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php', + 'OC\\Encryption\\Exceptions\\UnknownCipherException' => $baseDir . '/lib/private/Encryption/Exceptions/UnknownCipherException.php', + 'OC\\Encryption\\File' => $baseDir . '/lib/private/Encryption/File.php', + 'OC\\Encryption\\HookManager' => $baseDir . '/lib/private/Encryption/HookManager.php', + 'OC\\Encryption\\Keys\\Storage' => $baseDir . '/lib/private/Encryption/Keys/Storage.php', + 'OC\\Encryption\\Manager' => $baseDir . '/lib/private/Encryption/Manager.php', + 'OC\\Encryption\\Update' => $baseDir . '/lib/private/Encryption/Update.php', + 'OC\\Encryption\\Util' => $baseDir . '/lib/private/Encryption/Util.php', + 'OC\\EventDispatcher\\EventDispatcher' => $baseDir . '/lib/private/EventDispatcher/EventDispatcher.php', + 'OC\\EventDispatcher\\GenericEventWrapper' => $baseDir . '/lib/private/EventDispatcher/GenericEventWrapper.php', + 'OC\\EventDispatcher\\ServiceEventListener' => $baseDir . '/lib/private/EventDispatcher/ServiceEventListener.php', + 'OC\\EventDispatcher\\SymfonyAdapter' => $baseDir . '/lib/private/EventDispatcher/SymfonyAdapter.php', + 'OC\\Federation\\CloudFederationFactory' => $baseDir . '/lib/private/Federation/CloudFederationFactory.php', + 'OC\\Federation\\CloudFederationNotification' => $baseDir . '/lib/private/Federation/CloudFederationNotification.php', + 'OC\\Federation\\CloudFederationProviderManager' => $baseDir . '/lib/private/Federation/CloudFederationProviderManager.php', + 'OC\\Federation\\CloudFederationShare' => $baseDir . '/lib/private/Federation/CloudFederationShare.php', + 'OC\\Federation\\CloudId' => $baseDir . '/lib/private/Federation/CloudId.php', + 'OC\\Federation\\CloudIdManager' => $baseDir . '/lib/private/Federation/CloudIdManager.php', + 'OC\\Files\\AppData\\AppData' => $baseDir . '/lib/private/Files/AppData/AppData.php', + 'OC\\Files\\AppData\\Factory' => $baseDir . '/lib/private/Files/AppData/Factory.php', + 'OC\\Files\\Cache\\AbstractCacheEvent' => $baseDir . '/lib/private/Files/Cache/AbstractCacheEvent.php', + 'OC\\Files\\Cache\\Cache' => $baseDir . '/lib/private/Files/Cache/Cache.php', + 'OC\\Files\\Cache\\CacheEntry' => $baseDir . '/lib/private/Files/Cache/CacheEntry.php', + 'OC\\Files\\Cache\\CacheQueryBuilder' => $baseDir . '/lib/private/Files/Cache/CacheQueryBuilder.php', + 'OC\\Files\\Cache\\FailedCache' => $baseDir . '/lib/private/Files/Cache/FailedCache.php', + 'OC\\Files\\Cache\\HomeCache' => $baseDir . '/lib/private/Files/Cache/HomeCache.php', + 'OC\\Files\\Cache\\HomePropagator' => $baseDir . '/lib/private/Files/Cache/HomePropagator.php', + 'OC\\Files\\Cache\\LocalRootScanner' => $baseDir . '/lib/private/Files/Cache/LocalRootScanner.php', + 'OC\\Files\\Cache\\MoveFromCacheTrait' => $baseDir . '/lib/private/Files/Cache/MoveFromCacheTrait.php', + 'OC\\Files\\Cache\\NullWatcher' => $baseDir . '/lib/private/Files/Cache/NullWatcher.php', + 'OC\\Files\\Cache\\Propagator' => $baseDir . '/lib/private/Files/Cache/Propagator.php', + 'OC\\Files\\Cache\\QuerySearchHelper' => $baseDir . '/lib/private/Files/Cache/QuerySearchHelper.php', + 'OC\\Files\\Cache\\Scanner' => $baseDir . '/lib/private/Files/Cache/Scanner.php', + 'OC\\Files\\Cache\\Storage' => $baseDir . '/lib/private/Files/Cache/Storage.php', + 'OC\\Files\\Cache\\StorageGlobal' => $baseDir . '/lib/private/Files/Cache/StorageGlobal.php', + 'OC\\Files\\Cache\\Updater' => $baseDir . '/lib/private/Files/Cache/Updater.php', + 'OC\\Files\\Cache\\Watcher' => $baseDir . '/lib/private/Files/Cache/Watcher.php', + 'OC\\Files\\Cache\\Wrapper\\CacheJail' => $baseDir . '/lib/private/Files/Cache/Wrapper/CacheJail.php', + 'OC\\Files\\Cache\\Wrapper\\CachePermissionsMask' => $baseDir . '/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php', + 'OC\\Files\\Cache\\Wrapper\\CacheWrapper' => $baseDir . '/lib/private/Files/Cache/Wrapper/CacheWrapper.php', + 'OC\\Files\\Cache\\Wrapper\\JailPropagator' => $baseDir . '/lib/private/Files/Cache/Wrapper/JailPropagator.php', + 'OC\\Files\\Config\\CachedMountFileInfo' => $baseDir . '/lib/private/Files/Config/CachedMountFileInfo.php', + 'OC\\Files\\Config\\CachedMountInfo' => $baseDir . '/lib/private/Files/Config/CachedMountInfo.php', + 'OC\\Files\\Config\\LazyStorageMountInfo' => $baseDir . '/lib/private/Files/Config/LazyStorageMountInfo.php', + 'OC\\Files\\Config\\MountProviderCollection' => $baseDir . '/lib/private/Files/Config/MountProviderCollection.php', + 'OC\\Files\\Config\\UserMountCache' => $baseDir . '/lib/private/Files/Config/UserMountCache.php', + 'OC\\Files\\Config\\UserMountCacheListener' => $baseDir . '/lib/private/Files/Config/UserMountCacheListener.php', + 'OC\\Files\\FileInfo' => $baseDir . '/lib/private/Files/FileInfo.php', + 'OC\\Files\\Filesystem' => $baseDir . '/lib/private/Files/Filesystem.php', + 'OC\\Files\\Mount\\CacheMountProvider' => $baseDir . '/lib/private/Files/Mount/CacheMountProvider.php', + 'OC\\Files\\Mount\\LocalHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/LocalHomeMountProvider.php', + 'OC\\Files\\Mount\\Manager' => $baseDir . '/lib/private/Files/Mount/Manager.php', + 'OC\\Files\\Mount\\MountPoint' => $baseDir . '/lib/private/Files/Mount/MountPoint.php', + 'OC\\Files\\Mount\\MoveableMount' => $baseDir . '/lib/private/Files/Mount/MoveableMount.php', + 'OC\\Files\\Mount\\ObjectHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectHomeMountProvider.php', + 'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php', + 'OC\\Files\\Node\\File' => $baseDir . '/lib/private/Files/Node/File.php', + 'OC\\Files\\Node\\Folder' => $baseDir . '/lib/private/Files/Node/Folder.php', + 'OC\\Files\\Node\\HookConnector' => $baseDir . '/lib/private/Files/Node/HookConnector.php', + 'OC\\Files\\Node\\LazyFolder' => $baseDir . '/lib/private/Files/Node/LazyFolder.php', + 'OC\\Files\\Node\\LazyRoot' => $baseDir . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\Node' => $baseDir . '/lib/private/Files/Node/Node.php', + 'OC\\Files\\Node\\NonExistingFile' => $baseDir . '/lib/private/Files/Node/NonExistingFile.php', + 'OC\\Files\\Node\\NonExistingFolder' => $baseDir . '/lib/private/Files/Node/NonExistingFolder.php', + 'OC\\Files\\Node\\Root' => $baseDir . '/lib/private/Files/Node/Root.php', + 'OC\\Files\\Notify\\Change' => $baseDir . '/lib/private/Files/Notify/Change.php', + 'OC\\Files\\Notify\\RenameChange' => $baseDir . '/lib/private/Files/Notify/RenameChange.php', + 'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\Azure' => $baseDir . '/lib/private/Files/ObjectStore/Azure.php', + 'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php', + 'OC\\Files\\ObjectStore\\NoopScanner' => $baseDir . '/lib/private/Files/ObjectStore/NoopScanner.php', + 'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\S3' => $baseDir . '/lib/private/Files/ObjectStore/S3.php', + 'OC\\Files\\ObjectStore\\S3ConnectionTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php', + 'OC\\Files\\ObjectStore\\S3ObjectTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ObjectTrait.php', + 'OC\\Files\\ObjectStore\\S3Signature' => $baseDir . '/lib/private/Files/ObjectStore/S3Signature.php', + 'OC\\Files\\ObjectStore\\StorageObjectStore' => $baseDir . '/lib/private/Files/ObjectStore/StorageObjectStore.php', + 'OC\\Files\\ObjectStore\\Swift' => $baseDir . '/lib/private/Files/ObjectStore/Swift.php', + 'OC\\Files\\ObjectStore\\SwiftFactory' => $baseDir . '/lib/private/Files/ObjectStore/SwiftFactory.php', + 'OC\\Files\\ObjectStore\\SwiftV2CachingAuthService' => $baseDir . '/lib/private/Files/ObjectStore/SwiftV2CachingAuthService.php', + 'OC\\Files\\Search\\SearchBinaryOperator' => $baseDir . '/lib/private/Files/Search/SearchBinaryOperator.php', + 'OC\\Files\\Search\\SearchComparison' => $baseDir . '/lib/private/Files/Search/SearchComparison.php', + 'OC\\Files\\Search\\SearchOrder' => $baseDir . '/lib/private/Files/Search/SearchOrder.php', + 'OC\\Files\\Search\\SearchQuery' => $baseDir . '/lib/private/Files/Search/SearchQuery.php', + 'OC\\Files\\SimpleFS\\NewSimpleFile' => $baseDir . '/lib/private/Files/SimpleFS/NewSimpleFile.php', + 'OC\\Files\\SimpleFS\\SimpleFile' => $baseDir . '/lib/private/Files/SimpleFS/SimpleFile.php', + 'OC\\Files\\SimpleFS\\SimpleFolder' => $baseDir . '/lib/private/Files/SimpleFS/SimpleFolder.php', + 'OC\\Files\\Storage\\Common' => $baseDir . '/lib/private/Files/Storage/Common.php', + 'OC\\Files\\Storage\\CommonTest' => $baseDir . '/lib/private/Files/Storage/CommonTest.php', + 'OC\\Files\\Storage\\DAV' => $baseDir . '/lib/private/Files/Storage/DAV.php', + 'OC\\Files\\Storage\\FailedStorage' => $baseDir . '/lib/private/Files/Storage/FailedStorage.php', + 'OC\\Files\\Storage\\Flysystem' => $baseDir . '/lib/private/Files/Storage/Flysystem.php', + 'OC\\Files\\Storage\\Home' => $baseDir . '/lib/private/Files/Storage/Home.php', + 'OC\\Files\\Storage\\Local' => $baseDir . '/lib/private/Files/Storage/Local.php', + 'OC\\Files\\Storage\\LocalRootStorage' => $baseDir . '/lib/private/Files/Storage/LocalRootStorage.php', + 'OC\\Files\\Storage\\LocalTempFileTrait' => $baseDir . '/lib/private/Files/Storage/LocalTempFileTrait.php', + 'OC\\Files\\Storage\\PolyFill\\CopyDirectory' => $baseDir . '/lib/private/Files/Storage/PolyFill/CopyDirectory.php', + 'OC\\Files\\Storage\\Storage' => $baseDir . '/lib/private/Files/Storage/Storage.php', + 'OC\\Files\\Storage\\StorageFactory' => $baseDir . '/lib/private/Files/Storage/StorageFactory.php', + 'OC\\Files\\Storage\\Temporary' => $baseDir . '/lib/private/Files/Storage/Temporary.php', + 'OC\\Files\\Storage\\Wrapper\\Availability' => $baseDir . '/lib/private/Files/Storage/Wrapper/Availability.php', + 'OC\\Files\\Storage\\Wrapper\\Encoding' => $baseDir . '/lib/private/Files/Storage/Wrapper/Encoding.php', + 'OC\\Files\\Storage\\Wrapper\\Encryption' => $baseDir . '/lib/private/Files/Storage/Wrapper/Encryption.php', + 'OC\\Files\\Storage\\Wrapper\\Jail' => $baseDir . '/lib/private/Files/Storage/Wrapper/Jail.php', + 'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => $baseDir . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php', + 'OC\\Files\\Storage\\Wrapper\\Quota' => $baseDir . '/lib/private/Files/Storage/Wrapper/Quota.php', + 'OC\\Files\\Storage\\Wrapper\\Wrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/Wrapper.php', + 'OC\\Files\\Stream\\Encryption' => $baseDir . '/lib/private/Files/Stream/Encryption.php', + 'OC\\Files\\Stream\\HashWrapper' => $baseDir . '/lib/private/Files/Stream/HashWrapper.php', + 'OC\\Files\\Stream\\Quota' => $baseDir . '/lib/private/Files/Stream/Quota.php', + 'OC\\Files\\Stream\\SeekableHttpStream' => $baseDir . '/lib/private/Files/Stream/SeekableHttpStream.php', + 'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php', + 'OC\\Files\\Type\\Loader' => $baseDir . '/lib/private/Files/Type/Loader.php', + 'OC\\Files\\Type\\TemplateManager' => $baseDir . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\Scanner' => $baseDir . '/lib/private/Files/Utils/Scanner.php', + 'OC\\Files\\View' => $baseDir . '/lib/private/Files/View.php', + 'OC\\ForbiddenException' => $baseDir . '/lib/private/ForbiddenException.php', + 'OC\\FullTextSearch\\FullTextSearchManager' => $baseDir . '/lib/private/FullTextSearch/FullTextSearchManager.php', + 'OC\\FullTextSearch\\Model\\DocumentAccess' => $baseDir . '/lib/private/FullTextSearch/Model/DocumentAccess.php', + 'OC\\FullTextSearch\\Model\\IndexDocument' => $baseDir . '/lib/private/FullTextSearch/Model/IndexDocument.php', + 'OC\\FullTextSearch\\Model\\SearchOption' => $baseDir . '/lib/private/FullTextSearch/Model/SearchOption.php', + 'OC\\FullTextSearch\\Model\\SearchRequestSimpleQuery' => $baseDir . '/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php', + 'OC\\FullTextSearch\\Model\\SearchTemplate' => $baseDir . '/lib/private/FullTextSearch/Model/SearchTemplate.php', + 'OC\\GlobalScale\\Config' => $baseDir . '/lib/private/GlobalScale/Config.php', + 'OC\\Group\\Backend' => $baseDir . '/lib/private/Group/Backend.php', + 'OC\\Group\\Database' => $baseDir . '/lib/private/Group/Database.php', + 'OC\\Group\\Group' => $baseDir . '/lib/private/Group/Group.php', + 'OC\\Group\\Manager' => $baseDir . '/lib/private/Group/Manager.php', + 'OC\\Group\\MetaData' => $baseDir . '/lib/private/Group/MetaData.php', + 'OC\\HintException' => $baseDir . '/lib/private/HintException.php', + 'OC\\Hooks\\BasicEmitter' => $baseDir . '/lib/private/Hooks/BasicEmitter.php', + 'OC\\Hooks\\Emitter' => $baseDir . '/lib/private/Hooks/Emitter.php', + 'OC\\Hooks\\EmitterTrait' => $baseDir . '/lib/private/Hooks/EmitterTrait.php', + 'OC\\Hooks\\ForwardingEmitter' => $baseDir . '/lib/private/Hooks/ForwardingEmitter.php', + 'OC\\Hooks\\LegacyEmitter' => $baseDir . '/lib/private/Hooks/LegacyEmitter.php', + 'OC\\Hooks\\PublicEmitter' => $baseDir . '/lib/private/Hooks/PublicEmitter.php', + 'OC\\Http\\Client\\Client' => $baseDir . '/lib/private/Http/Client/Client.php', + 'OC\\Http\\Client\\ClientService' => $baseDir . '/lib/private/Http/Client/ClientService.php', + 'OC\\Http\\Client\\Response' => $baseDir . '/lib/private/Http/Client/Response.php', + 'OC\\Http\\CookieHelper' => $baseDir . '/lib/private/Http/CookieHelper.php', + 'OC\\InitialStateService' => $baseDir . '/lib/private/InitialStateService.php', + 'OC\\Installer' => $baseDir . '/lib/private/Installer.php', + 'OC\\IntegrityCheck\\Checker' => $baseDir . '/lib/private/IntegrityCheck/Checker.php', + 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException' => $baseDir . '/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php', + 'OC\\IntegrityCheck\\Helpers\\AppLocator' => $baseDir . '/lib/private/IntegrityCheck/Helpers/AppLocator.php', + 'OC\\IntegrityCheck\\Helpers\\EnvironmentHelper' => $baseDir . '/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php', + 'OC\\IntegrityCheck\\Helpers\\FileAccessHelper' => $baseDir . '/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php', + 'OC\\IntegrityCheck\\Iterator\\ExcludeFileByNameFilterIterator' => $baseDir . '/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php', + 'OC\\IntegrityCheck\\Iterator\\ExcludeFoldersByPathFilterIterator' => $baseDir . '/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php', + 'OC\\L10N\\Factory' => $baseDir . '/lib/private/L10N/Factory.php', + 'OC\\L10N\\L10N' => $baseDir . '/lib/private/L10N/L10N.php', + 'OC\\L10N\\L10NString' => $baseDir . '/lib/private/L10N/L10NString.php', + 'OC\\L10N\\LanguageIterator' => $baseDir . '/lib/private/L10N/LanguageIterator.php', + 'OC\\L10N\\LanguageNotFoundException' => $baseDir . '/lib/private/L10N/LanguageNotFoundException.php', + 'OC\\L10N\\LazyL10N' => $baseDir . '/lib/private/L10N/LazyL10N.php', + 'OC\\LargeFileHelper' => $baseDir . '/lib/private/LargeFileHelper.php', + 'OC\\Lock\\AbstractLockingProvider' => $baseDir . '/lib/private/Lock/AbstractLockingProvider.php', + 'OC\\Lock\\DBLockingProvider' => $baseDir . '/lib/private/Lock/DBLockingProvider.php', + 'OC\\Lock\\MemcacheLockingProvider' => $baseDir . '/lib/private/Lock/MemcacheLockingProvider.php', + 'OC\\Lock\\NoopLockingProvider' => $baseDir . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => $baseDir . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => $baseDir . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => $baseDir . '/lib/private/Lockdown/LockdownManager.php', + 'OC\\Log' => $baseDir . '/lib/private/Log.php', + 'OC\\Log\\ErrorHandler' => $baseDir . '/lib/private/Log/ErrorHandler.php', + 'OC\\Log\\Errorlog' => $baseDir . '/lib/private/Log/Errorlog.php', + 'OC\\Log\\ExceptionSerializer' => $baseDir . '/lib/private/Log/ExceptionSerializer.php', + 'OC\\Log\\File' => $baseDir . '/lib/private/Log/File.php', + 'OC\\Log\\LogDetails' => $baseDir . '/lib/private/Log/LogDetails.php', + 'OC\\Log\\LogFactory' => $baseDir . '/lib/private/Log/LogFactory.php', + 'OC\\Log\\PsrLoggerAdapter' => $baseDir . '/lib/private/Log/PsrLoggerAdapter.php', + 'OC\\Log\\Rotate' => $baseDir . '/lib/private/Log/Rotate.php', + 'OC\\Log\\Syslog' => $baseDir . '/lib/private/Log/Syslog.php', + 'OC\\Log\\Systemdlog' => $baseDir . '/lib/private/Log/Systemdlog.php', + 'OC\\Mail\\Attachment' => $baseDir . '/lib/private/Mail/Attachment.php', + 'OC\\Mail\\EMailTemplate' => $baseDir . '/lib/private/Mail/EMailTemplate.php', + 'OC\\Mail\\Mailer' => $baseDir . '/lib/private/Mail/Mailer.php', + 'OC\\Mail\\Message' => $baseDir . '/lib/private/Mail/Message.php', + 'OC\\Memcache\\APCu' => $baseDir . '/lib/private/Memcache/APCu.php', + 'OC\\Memcache\\ArrayCache' => $baseDir . '/lib/private/Memcache/ArrayCache.php', + 'OC\\Memcache\\CADTrait' => $baseDir . '/lib/private/Memcache/CADTrait.php', + 'OC\\Memcache\\CASTrait' => $baseDir . '/lib/private/Memcache/CASTrait.php', + 'OC\\Memcache\\Cache' => $baseDir . '/lib/private/Memcache/Cache.php', + 'OC\\Memcache\\Factory' => $baseDir . '/lib/private/Memcache/Factory.php', + 'OC\\Memcache\\Memcached' => $baseDir . '/lib/private/Memcache/Memcached.php', + 'OC\\Memcache\\NullCache' => $baseDir . '/lib/private/Memcache/NullCache.php', + 'OC\\Memcache\\Redis' => $baseDir . '/lib/private/Memcache/Redis.php', + 'OC\\MemoryInfo' => $baseDir . '/lib/private/MemoryInfo.php', + 'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php', + 'OC\\Migration\\ConsoleOutput' => $baseDir . '/lib/private/Migration/ConsoleOutput.php', + 'OC\\Migration\\SimpleOutput' => $baseDir . '/lib/private/Migration/SimpleOutput.php', + 'OC\\NaturalSort' => $baseDir . '/lib/private/NaturalSort.php', + 'OC\\NaturalSort_DefaultCollator' => $baseDir . '/lib/private/NaturalSort_DefaultCollator.php', + 'OC\\NavigationManager' => $baseDir . '/lib/private/NavigationManager.php', + 'OC\\NeedsUpdateException' => $baseDir . '/lib/private/NeedsUpdateException.php', + 'OC\\NotSquareException' => $baseDir . '/lib/private/NotSquareException.php', + 'OC\\Notification\\Action' => $baseDir . '/lib/private/Notification/Action.php', + 'OC\\Notification\\Manager' => $baseDir . '/lib/private/Notification/Manager.php', + 'OC\\Notification\\Notification' => $baseDir . '/lib/private/Notification/Notification.php', + 'OC\\OCS\\CoreCapabilities' => $baseDir . '/lib/private/OCS/CoreCapabilities.php', + 'OC\\OCS\\DiscoveryService' => $baseDir . '/lib/private/OCS/DiscoveryService.php', + 'OC\\OCS\\Exception' => $baseDir . '/lib/private/OCS/Exception.php', + 'OC\\OCS\\Provider' => $baseDir . '/lib/private/OCS/Provider.php', + 'OC\\OCS\\Result' => $baseDir . '/lib/private/OCS/Result.php', + 'OC\\PreviewManager' => $baseDir . '/lib/private/PreviewManager.php', + 'OC\\PreviewNotAvailableException' => $baseDir . '/lib/private/PreviewNotAvailableException.php', + 'OC\\Preview\\BMP' => $baseDir . '/lib/private/Preview/BMP.php', + 'OC\\Preview\\BackgroundCleanupJob' => $baseDir . '/lib/private/Preview/BackgroundCleanupJob.php', + 'OC\\Preview\\Bitmap' => $baseDir . '/lib/private/Preview/Bitmap.php', + 'OC\\Preview\\Bundled' => $baseDir . '/lib/private/Preview/Bundled.php', + 'OC\\Preview\\Font' => $baseDir . '/lib/private/Preview/Font.php', + 'OC\\Preview\\GIF' => $baseDir . '/lib/private/Preview/GIF.php', + 'OC\\Preview\\Generator' => $baseDir . '/lib/private/Preview/Generator.php', + 'OC\\Preview\\GeneratorHelper' => $baseDir . '/lib/private/Preview/GeneratorHelper.php', + 'OC\\Preview\\HEIC' => $baseDir . '/lib/private/Preview/HEIC.php', + 'OC\\Preview\\Illustrator' => $baseDir . '/lib/private/Preview/Illustrator.php', + 'OC\\Preview\\Image' => $baseDir . '/lib/private/Preview/Image.php', + 'OC\\Preview\\JPEG' => $baseDir . '/lib/private/Preview/JPEG.php', + 'OC\\Preview\\Krita' => $baseDir . '/lib/private/Preview/Krita.php', + 'OC\\Preview\\MP3' => $baseDir . '/lib/private/Preview/MP3.php', + 'OC\\Preview\\MSOffice2003' => $baseDir . '/lib/private/Preview/MSOffice2003.php', + 'OC\\Preview\\MSOffice2007' => $baseDir . '/lib/private/Preview/MSOffice2007.php', + 'OC\\Preview\\MSOfficeDoc' => $baseDir . '/lib/private/Preview/MSOfficeDoc.php', + 'OC\\Preview\\MarkDown' => $baseDir . '/lib/private/Preview/MarkDown.php', + 'OC\\Preview\\Movie' => $baseDir . '/lib/private/Preview/Movie.php', + 'OC\\Preview\\Office' => $baseDir . '/lib/private/Preview/Office.php', + 'OC\\Preview\\OpenDocument' => $baseDir . '/lib/private/Preview/OpenDocument.php', + 'OC\\Preview\\PDF' => $baseDir . '/lib/private/Preview/PDF.php', + 'OC\\Preview\\PNG' => $baseDir . '/lib/private/Preview/PNG.php', + 'OC\\Preview\\Photoshop' => $baseDir . '/lib/private/Preview/Photoshop.php', + 'OC\\Preview\\Postscript' => $baseDir . '/lib/private/Preview/Postscript.php', + 'OC\\Preview\\Provider' => $baseDir . '/lib/private/Preview/Provider.php', + 'OC\\Preview\\ProviderV1Adapter' => $baseDir . '/lib/private/Preview/ProviderV1Adapter.php', + 'OC\\Preview\\ProviderV2' => $baseDir . '/lib/private/Preview/ProviderV2.php', + 'OC\\Preview\\SVG' => $baseDir . '/lib/private/Preview/SVG.php', + 'OC\\Preview\\StarOffice' => $baseDir . '/lib/private/Preview/StarOffice.php', + 'OC\\Preview\\Storage\\Root' => $baseDir . '/lib/private/Preview/Storage/Root.php', + 'OC\\Preview\\TIFF' => $baseDir . '/lib/private/Preview/TIFF.php', + 'OC\\Preview\\TXT' => $baseDir . '/lib/private/Preview/TXT.php', + 'OC\\Preview\\Watcher' => $baseDir . '/lib/private/Preview/Watcher.php', + 'OC\\Preview\\WatcherConnector' => $baseDir . '/lib/private/Preview/WatcherConnector.php', + 'OC\\Preview\\XBitmap' => $baseDir . '/lib/private/Preview/XBitmap.php', + 'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php', + 'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php', + 'OC\\Remote\\Api\\ApiCollection' => $baseDir . '/lib/private/Remote/Api/ApiCollection.php', + 'OC\\Remote\\Api\\ApiFactory' => $baseDir . '/lib/private/Remote/Api/ApiFactory.php', + 'OC\\Remote\\Api\\NotFoundException' => $baseDir . '/lib/private/Remote/Api/NotFoundException.php', + 'OC\\Remote\\Api\\OCS' => $baseDir . '/lib/private/Remote/Api/OCS.php', + 'OC\\Remote\\Credentials' => $baseDir . '/lib/private/Remote/Credentials.php', + 'OC\\Remote\\Instance' => $baseDir . '/lib/private/Remote/Instance.php', + 'OC\\Remote\\InstanceFactory' => $baseDir . '/lib/private/Remote/InstanceFactory.php', + 'OC\\Remote\\User' => $baseDir . '/lib/private/Remote/User.php', + 'OC\\Repair' => $baseDir . '/lib/private/Repair.php', + 'OC\\RepairException' => $baseDir . '/lib/private/RepairException.php', + 'OC\\Repair\\AddCleanupUpdaterBackupsJob' => $baseDir . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php', + 'OC\\Repair\\CleanTags' => $baseDir . '/lib/private/Repair/CleanTags.php', + 'OC\\Repair\\ClearFrontendCaches' => $baseDir . '/lib/private/Repair/ClearFrontendCaches.php', + 'OC\\Repair\\ClearGeneratedAvatarCache' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCache.php', + 'OC\\Repair\\Collation' => $baseDir . '/lib/private/Repair/Collation.php', + 'OC\\Repair\\MoveUpdaterStepFile' => $baseDir . '/lib/private/Repair/MoveUpdaterStepFile.php', + 'OC\\Repair\\NC11\\FixMountStorages' => $baseDir . '/lib/private/Repair/NC11/FixMountStorages.php', + 'OC\\Repair\\NC13\\AddLogRotateJob' => $baseDir . '/lib/private/Repair/NC13/AddLogRotateJob.php', + 'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => $baseDir . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php', + 'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => $baseDir . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php', + 'OC\\Repair\\NC16\\CleanupCardDAVPhotoCache' => $baseDir . '/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php', + 'OC\\Repair\\NC16\\ClearCollectionsAccessCache' => $baseDir . '/lib/private/Repair/NC16/ClearCollectionsAccessCache.php', + 'OC\\Repair\\NC18\\ResetGeneratedAvatarFlag' => $baseDir . '/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php', + 'OC\\Repair\\NC20\\EncryptionLegacyCipher' => $baseDir . '/lib/private/Repair/NC20/EncryptionLegacyCipher.php', + 'OC\\Repair\\NC20\\EncryptionMigration' => $baseDir . '/lib/private/Repair/NC20/EncryptionMigration.php', + 'OC\\Repair\\NC20\\ShippedDashboardEnable' => $baseDir . '/lib/private/Repair/NC20/ShippedDashboardEnable.php', + 'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php', + 'OC\\Repair\\Owncloud\\DropAccountTermsTable' => $baseDir . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php', + 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => $baseDir . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', + 'OC\\Repair\\RemoveLinkShares' => $baseDir . '/lib/private/Repair/RemoveLinkShares.php', + 'OC\\Repair\\RepairInvalidShares' => $baseDir . '/lib/private/Repair/RepairInvalidShares.php', + 'OC\\Repair\\RepairMimeTypes' => $baseDir . '/lib/private/Repair/RepairMimeTypes.php', + 'OC\\Repair\\SqliteAutoincrement' => $baseDir . '/lib/private/Repair/SqliteAutoincrement.php', + 'OC\\RichObjectStrings\\Validator' => $baseDir . '/lib/private/RichObjectStrings/Validator.php', + 'OC\\Route\\CachingRouter' => $baseDir . '/lib/private/Route/CachingRouter.php', + 'OC\\Route\\Route' => $baseDir . '/lib/private/Route/Route.php', + 'OC\\Route\\Router' => $baseDir . '/lib/private/Route/Router.php', + 'OC\\Search' => $baseDir . '/lib/private/Search.php', + 'OC\\Search\\Provider\\File' => $baseDir . '/lib/private/Search/Provider/File.php', + 'OC\\Search\\Result\\Audio' => $baseDir . '/lib/private/Search/Result/Audio.php', + 'OC\\Search\\Result\\File' => $baseDir . '/lib/private/Search/Result/File.php', + 'OC\\Search\\Result\\Folder' => $baseDir . '/lib/private/Search/Result/Folder.php', + 'OC\\Search\\Result\\Image' => $baseDir . '/lib/private/Search/Result/Image.php', + 'OC\\Search\\SearchComposer' => $baseDir . '/lib/private/Search/SearchComposer.php', + 'OC\\Search\\SearchQuery' => $baseDir . '/lib/private/Search/SearchQuery.php', + 'OC\\Security\\Bruteforce\\Capabilities' => $baseDir . '/lib/private/Security/Bruteforce/Capabilities.php', + 'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php', + 'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php', + 'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php', + 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php', + 'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php', + 'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php', + 'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php', + 'OC\\Security\\CSRF\\TokenStorage\\SessionStorage' => $baseDir . '/lib/private/Security/CSRF/TokenStorage/SessionStorage.php', + 'OC\\Security\\Certificate' => $baseDir . '/lib/private/Security/Certificate.php', + 'OC\\Security\\CertificateManager' => $baseDir . '/lib/private/Security/CertificateManager.php', + 'OC\\Security\\CredentialsManager' => $baseDir . '/lib/private/Security/CredentialsManager.php', + 'OC\\Security\\Crypto' => $baseDir . '/lib/private/Security/Crypto.php', + 'OC\\Security\\FeaturePolicy\\FeaturePolicy' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', + 'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', + 'OC\\Security\\Hasher' => $baseDir . '/lib/private/Security/Hasher.php', + 'OC\\Security\\IdentityProof\\Key' => $baseDir . '/lib/private/Security/IdentityProof/Key.php', + 'OC\\Security\\IdentityProof\\Manager' => $baseDir . '/lib/private/Security/IdentityProof/Manager.php', + 'OC\\Security\\IdentityProof\\Signer' => $baseDir . '/lib/private/Security/IdentityProof/Signer.php', + 'OC\\Security\\Normalizer\\IpAddress' => $baseDir . '/lib/private/Security/Normalizer/IpAddress.php', + 'OC\\Security\\RateLimiting\\Backend\\IBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/IBackend.php', + 'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => $baseDir . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php', + 'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => $baseDir . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php', + 'OC\\Security\\RateLimiting\\Limiter' => $baseDir . '/lib/private/Security/RateLimiting/Limiter.php', + 'OC\\Security\\SecureRandom' => $baseDir . '/lib/private/Security/SecureRandom.php', + 'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php', + 'OC\\Server' => $baseDir . '/lib/private/Server.php', + 'OC\\ServerContainer' => $baseDir . '/lib/private/ServerContainer.php', + 'OC\\ServerNotAvailableException' => $baseDir . '/lib/private/ServerNotAvailableException.php', + 'OC\\ServiceUnavailableException' => $baseDir . '/lib/private/ServiceUnavailableException.php', + 'OC\\Session\\CryptoSessionData' => $baseDir . '/lib/private/Session/CryptoSessionData.php', + 'OC\\Session\\CryptoWrapper' => $baseDir . '/lib/private/Session/CryptoWrapper.php', + 'OC\\Session\\Internal' => $baseDir . '/lib/private/Session/Internal.php', + 'OC\\Session\\Memory' => $baseDir . '/lib/private/Session/Memory.php', + 'OC\\Session\\Session' => $baseDir . '/lib/private/Session/Session.php', + 'OC\\Settings\\Manager' => $baseDir . '/lib/private/Settings/Manager.php', + 'OC\\Settings\\Section' => $baseDir . '/lib/private/Settings/Section.php', + 'OC\\Setup' => $baseDir . '/lib/private/Setup.php', + 'OC\\Setup\\AbstractDatabase' => $baseDir . '/lib/private/Setup/AbstractDatabase.php', + 'OC\\Setup\\MySQL' => $baseDir . '/lib/private/Setup/MySQL.php', + 'OC\\Setup\\OCI' => $baseDir . '/lib/private/Setup/OCI.php', + 'OC\\Setup\\PostgreSQL' => $baseDir . '/lib/private/Setup/PostgreSQL.php', + 'OC\\Setup\\Sqlite' => $baseDir . '/lib/private/Setup/Sqlite.php', + 'OC\\Share20\\DefaultShareProvider' => $baseDir . '/lib/private/Share20/DefaultShareProvider.php', + 'OC\\Share20\\Exception\\BackendError' => $baseDir . '/lib/private/Share20/Exception/BackendError.php', + 'OC\\Share20\\Exception\\InvalidShare' => $baseDir . '/lib/private/Share20/Exception/InvalidShare.php', + 'OC\\Share20\\Exception\\ProviderException' => $baseDir . '/lib/private/Share20/Exception/ProviderException.php', + 'OC\\Share20\\Hooks' => $baseDir . '/lib/private/Share20/Hooks.php', + 'OC\\Share20\\LegacyHooks' => $baseDir . '/lib/private/Share20/LegacyHooks.php', + 'OC\\Share20\\Manager' => $baseDir . '/lib/private/Share20/Manager.php', + 'OC\\Share20\\ProviderFactory' => $baseDir . '/lib/private/Share20/ProviderFactory.php', + 'OC\\Share20\\Share' => $baseDir . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareHelper' => $baseDir . '/lib/private/Share20/ShareHelper.php', + 'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php', + 'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php', + 'OC\\Share\\Helper' => $baseDir . '/lib/private/Share/Helper.php', + 'OC\\Share\\SearchResultSorter' => $baseDir . '/lib/private/Share/SearchResultSorter.php', + 'OC\\Share\\Share' => $baseDir . '/lib/private/Share/Share.php', + 'OC\\Streamer' => $baseDir . '/lib/private/Streamer.php', + 'OC\\SubAdmin' => $baseDir . '/lib/private/SubAdmin.php', + 'OC\\Support\\CrashReport\\Registry' => $baseDir . '/lib/private/Support/CrashReport/Registry.php', + 'OC\\Support\\Subscription\\Registry' => $baseDir . '/lib/private/Support/Subscription/Registry.php', + 'OC\\SystemConfig' => $baseDir . '/lib/private/SystemConfig.php', + 'OC\\SystemTag\\ManagerFactory' => $baseDir . '/lib/private/SystemTag/ManagerFactory.php', + 'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php', + 'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php', + 'OC\\SystemTag\\SystemTagObjectMapper' => $baseDir . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\TagManager' => $baseDir . '/lib/private/TagManager.php', + 'OC\\Tagging\\Tag' => $baseDir . '/lib/private/Tagging/Tag.php', + 'OC\\Tagging\\TagMapper' => $baseDir . '/lib/private/Tagging/TagMapper.php', + 'OC\\Tags' => $baseDir . '/lib/private/Tags.php', + 'OC\\TempManager' => $baseDir . '/lib/private/TempManager.php', + 'OC\\TemplateLayout' => $baseDir . '/lib/private/TemplateLayout.php', + 'OC\\Template\\Base' => $baseDir . '/lib/private/Template/Base.php', + 'OC\\Template\\CSSResourceLocator' => $baseDir . '/lib/private/Template/CSSResourceLocator.php', + 'OC\\Template\\IconsCacher' => $baseDir . '/lib/private/Template/IconsCacher.php', + 'OC\\Template\\JSCombiner' => $baseDir . '/lib/private/Template/JSCombiner.php', + 'OC\\Template\\JSConfigHelper' => $baseDir . '/lib/private/Template/JSConfigHelper.php', + 'OC\\Template\\JSResourceLocator' => $baseDir . '/lib/private/Template/JSResourceLocator.php', + 'OC\\Template\\ResourceLocator' => $baseDir . '/lib/private/Template/ResourceLocator.php', + 'OC\\Template\\ResourceNotFoundException' => $baseDir . '/lib/private/Template/ResourceNotFoundException.php', + 'OC\\Template\\SCSSCacher' => $baseDir . '/lib/private/Template/SCSSCacher.php', + 'OC\\Template\\TemplateFileLocator' => $baseDir . '/lib/private/Template/TemplateFileLocator.php', + 'OC\\URLGenerator' => $baseDir . '/lib/private/URLGenerator.php', + 'OC\\Updater' => $baseDir . '/lib/private/Updater.php', + 'OC\\Updater\\ChangesCheck' => $baseDir . '/lib/private/Updater/ChangesCheck.php', + 'OC\\Updater\\ChangesMapper' => $baseDir . '/lib/private/Updater/ChangesMapper.php', + 'OC\\Updater\\ChangesResult' => $baseDir . '/lib/private/Updater/ChangesResult.php', + 'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php', + 'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php', + 'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php', + 'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php', + 'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php', + 'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php', + 'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', + 'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php', + 'OC\\User\\User' => $baseDir . '/lib/private/User/User.php', + 'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php', + 'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php', + 'OC_DB' => $baseDir . '/lib/private/legacy/OC_DB.php', + 'OC_DB_StatementWrapper' => $baseDir . '/lib/private/legacy/OC_DB_StatementWrapper.php', + 'OC_Defaults' => $baseDir . '/lib/private/legacy/OC_Defaults.php', + 'OC_EventSource' => $baseDir . '/lib/private/legacy/OC_EventSource.php', + 'OC_FileChunking' => $baseDir . '/lib/private/legacy/OC_FileChunking.php', + 'OC_Files' => $baseDir . '/lib/private/legacy/OC_Files.php', + 'OC_Helper' => $baseDir . '/lib/private/legacy/OC_Helper.php', + 'OC_Hook' => $baseDir . '/lib/private/legacy/OC_Hook.php', + 'OC_Image' => $baseDir . '/lib/private/legacy/OC_Image.php', + 'OC_JSON' => $baseDir . '/lib/private/legacy/OC_JSON.php', + 'OC_Response' => $baseDir . '/lib/private/legacy/OC_Response.php', + 'OC_Template' => $baseDir . '/lib/private/legacy/OC_Template.php', + 'OC_User' => $baseDir . '/lib/private/legacy/OC_User.php', + 'OC_Util' => $baseDir . '/lib/private/legacy/OC_Util.php', +); diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/autoload_namespaces.php b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_namespaces.php new file mode 100644 index 0000000..4a9c20b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/core'), + 'OC\\' => array($baseDir . '/lib/private'), + 'OCP\\' => array($baseDir . '/lib/public'), + '' => array($baseDir . '/lib/private/legacy'), +); diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/autoload_real.php b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_real.php new file mode 100644 index 0000000..a17f25c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_real.php @@ -0,0 +1,55 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/autoload_static.php b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_static.php new file mode 100644 index 0000000..e584c63 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/autoload_static.php @@ -0,0 +1,1430 @@ + + array ( + 'OC\\Core\\' => 8, + 'OC\\' => 3, + 'OCP\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'OC\\Core\\' => + array ( + 0 => __DIR__ . '/../../..' . '/core', + ), + 'OC\\' => + array ( + 0 => __DIR__ . '/../../..' . '/lib/private', + ), + 'OCP\\' => + array ( + 0 => __DIR__ . '/../../..' . '/lib/public', + ), + ); + + public static $fallbackDirsPsr4 = array ( + 0 => __DIR__ . '/../../..' . '/lib/private/legacy', + ); + + public static $classMap = array ( + 'OCP\\API' => __DIR__ . '/../../..' . '/lib/public/API.php', + 'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php', + 'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php', + 'OCP\\Accounts\\IAccountProperty' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountProperty.php', + 'OCP\\Accounts\\PropertyDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Accounts/PropertyDoesNotExistException.php', + 'OCP\\Activity\\ActivitySettings' => __DIR__ . '/../../..' . '/lib/public/Activity/ActivitySettings.php', + 'OCP\\Activity\\IConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IConsumer.php', + 'OCP\\Activity\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Activity/IEvent.php', + 'OCP\\Activity\\IEventMerger' => __DIR__ . '/../../..' . '/lib/public/Activity/IEventMerger.php', + 'OCP\\Activity\\IExtension' => __DIR__ . '/../../..' . '/lib/public/Activity/IExtension.php', + 'OCP\\Activity\\IFilter' => __DIR__ . '/../../..' . '/lib/public/Activity/IFilter.php', + 'OCP\\Activity\\IManager' => __DIR__ . '/../../..' . '/lib/public/Activity/IManager.php', + 'OCP\\Activity\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Activity/IProvider.php', + 'OCP\\Activity\\ISetting' => __DIR__ . '/../../..' . '/lib/public/Activity/ISetting.php', + 'OCP\\App' => __DIR__ . '/../../..' . '/lib/public/App.php', + 'OCP\\AppFramework\\ApiController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/ApiController.php', + 'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php', + 'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php', + 'OCP\\AppFramework\\Bootstrap\\IBootContext' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IBootContext.php', + 'OCP\\AppFramework\\Bootstrap\\IBootstrap' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IBootstrap.php', + 'OCP\\AppFramework\\Bootstrap\\IRegistrationContext' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Bootstrap/IRegistrationContext.php', + 'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php', + 'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php', + 'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php', + 'OCP\\AppFramework\\Db\\IMapperException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/IMapperException.php', + 'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php', + 'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php', + 'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php', + 'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php', + 'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php', + 'OCP\\AppFramework\\Http\\DataDownloadResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDownloadResponse.php', + 'OCP\\AppFramework\\Http\\DataResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataResponse.php', + 'OCP\\AppFramework\\Http\\DownloadResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DownloadResponse.php', + 'OCP\\AppFramework\\Http\\EmptyContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\EmptyFeaturePolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/EmptyFeaturePolicy.php', + 'OCP\\AppFramework\\Http\\Events\\BeforeTemplateRenderedEvent' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php', + 'OCP\\AppFramework\\Http\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FeaturePolicy.php', + 'OCP\\AppFramework\\Http\\FileDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FileDisplayResponse.php', + 'OCP\\AppFramework\\Http\\ICallbackResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ICallbackResponse.php', + 'OCP\\AppFramework\\Http\\IOutput' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/IOutput.php', + 'OCP\\AppFramework\\Http\\JSONResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/JSONResponse.php', + 'OCP\\AppFramework\\Http\\NotFoundResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/NotFoundResponse.php', + 'OCP\\AppFramework\\Http\\OCSResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/OCSResponse.php', + 'OCP\\AppFramework\\Http\\RedirectResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectResponse.php', + 'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php', + 'OCP\\AppFramework\\Http\\Response' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Response.php', + 'OCP\\AppFramework\\Http\\StandaloneTemplateResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StandaloneTemplateResponse.php', + 'OCP\\AppFramework\\Http\\StreamResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StreamResponse.php', + 'OCP\\AppFramework\\Http\\StrictContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\StrictEvalContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\StrictInlineContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php', + 'OCP\\AppFramework\\Http\\TemplateResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/TemplateResponse.php', + 'OCP\\AppFramework\\Http\\Template\\ExternalShareMenuAction' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\IMenuAction' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Template/IMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\LinkMenuAction' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Template/LinkMenuAction.php', + 'OCP\\AppFramework\\Http\\Template\\PublicTemplateResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php', + 'OCP\\AppFramework\\Http\\Template\\SimpleMenuAction' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Template/SimpleMenuAction.php', + 'OCP\\AppFramework\\Http\\TooManyRequestsResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/TooManyRequestsResponse.php', + 'OCP\\AppFramework\\Http\\ZipResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ZipResponse.php', + 'OCP\\AppFramework\\IAppContainer' => __DIR__ . '/../../..' . '/lib/public/AppFramework/IAppContainer.php', + 'OCP\\AppFramework\\Middleware' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Middleware.php', + 'OCP\\AppFramework\\OCSController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCSController.php', + 'OCP\\AppFramework\\OCS\\OCSBadRequestException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSBadRequestException.php', + 'OCP\\AppFramework\\OCS\\OCSException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSException.php', + 'OCP\\AppFramework\\OCS\\OCSForbiddenException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSForbiddenException.php', + 'OCP\\AppFramework\\OCS\\OCSNotFoundException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSNotFoundException.php', + 'OCP\\AppFramework\\PublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/PublicShareController.php', + 'OCP\\AppFramework\\QueryException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/QueryException.php', + 'OCP\\AppFramework\\Services\\IAppConfig' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Services/IAppConfig.php', + 'OCP\\AppFramework\\Services\\IInitialState' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Services/IInitialState.php', + 'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php', + 'OCP\\AppFramework\\Utility\\ITimeFactory' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/ITimeFactory.php', + 'OCP\\App\\AppPathNotFoundException' => __DIR__ . '/../../..' . '/lib/public/App/AppPathNotFoundException.php', + 'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php', + 'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.php', + 'OCP\\Authentication\\Events\\LoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/LoginFailedEvent.php', + 'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php', + 'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php', + 'OCP\\Authentication\\IAlternativeLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/IAlternativeLogin.php', + 'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php', + 'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php', + 'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php', + 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php', + 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php', + 'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php', + 'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php', + 'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php', + 'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php', + 'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php', + 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', + 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', + 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderDisabled' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderDisabled.php', + 'OCP\\AutoloadNotAllowedException' => __DIR__ . '/../../..' . '/lib/public/AutoloadNotAllowedException.php', + 'OCP\\BackgroundJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob.php', + 'OCP\\BackgroundJob\\IJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/IJob.php', + 'OCP\\BackgroundJob\\IJobList' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/IJobList.php', + 'OCP\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/Job.php', + 'OCP\\BackgroundJob\\QueuedJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/QueuedJob.php', + 'OCP\\BackgroundJob\\TimedJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/TimedJob.php', + 'OCP\\Broadcast\\Events\\IBroadcastEvent' => __DIR__ . '/../../..' . '/lib/public/Broadcast/Events/IBroadcastEvent.php', + 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', + 'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php', + 'OCP\\Calendar\\IManager' => __DIR__ . '/../../..' . '/lib/public/Calendar/IManager.php', + 'OCP\\Calendar\\IMetadataProvider' => __DIR__ . '/../../..' . '/lib/public/Calendar/IMetadataProvider.php', + 'OCP\\Calendar\\Resource\\IBackend' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IBackend.php', + 'OCP\\Calendar\\Resource\\IManager' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IManager.php', + 'OCP\\Calendar\\Resource\\IResource' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IResource.php', + 'OCP\\Calendar\\Resource\\IResourceMetadata' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IResourceMetadata.php', + 'OCP\\Calendar\\Room\\IBackend' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IBackend.php', + 'OCP\\Calendar\\Room\\IManager' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IManager.php', + 'OCP\\Calendar\\Room\\IRoom' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IRoom.php', + 'OCP\\Calendar\\Room\\IRoomMetadata' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IRoomMetadata.php', + 'OCP\\Capabilities\\ICapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/ICapability.php', + 'OCP\\Capabilities\\IPublicCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IPublicCapability.php', + 'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php', + 'OCP\\Collaboration\\AutoComplete\\IManager' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/IManager.php', + 'OCP\\Collaboration\\AutoComplete\\ISorter' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/ISorter.php', + 'OCP\\Collaboration\\Collaborators\\ISearch' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearch.php', + 'OCP\\Collaboration\\Collaborators\\ISearchPlugin' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearchPlugin.php', + 'OCP\\Collaboration\\Collaborators\\ISearchResult' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearchResult.php', + 'OCP\\Collaboration\\Collaborators\\SearchResultType' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/SearchResultType.php', + 'OCP\\Collaboration\\Resources\\CollectionException' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/CollectionException.php', + 'OCP\\Collaboration\\Resources\\ICollection' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/ICollection.php', + 'OCP\\Collaboration\\Resources\\IManager' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/IManager.php', + 'OCP\\Collaboration\\Resources\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/IProvider.php', + 'OCP\\Collaboration\\Resources\\IProviderManager' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/IProviderManager.php', + 'OCP\\Collaboration\\Resources\\IResource' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/IResource.php', + 'OCP\\Collaboration\\Resources\\ResourceException' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/ResourceException.php', + 'OCP\\Command\\IBus' => __DIR__ . '/../../..' . '/lib/public/Command/IBus.php', + 'OCP\\Command\\ICommand' => __DIR__ . '/../../..' . '/lib/public/Command/ICommand.php', + 'OCP\\Comments\\CommentsEntityEvent' => __DIR__ . '/../../..' . '/lib/public/Comments/CommentsEntityEvent.php', + 'OCP\\Comments\\CommentsEvent' => __DIR__ . '/../../..' . '/lib/public/Comments/CommentsEvent.php', + 'OCP\\Comments\\IComment' => __DIR__ . '/../../..' . '/lib/public/Comments/IComment.php', + 'OCP\\Comments\\ICommentsEventHandler' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsEventHandler.php', + 'OCP\\Comments\\ICommentsManager' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsManager.php', + 'OCP\\Comments\\ICommentsManagerFactory' => __DIR__ . '/../../..' . '/lib/public/Comments/ICommentsManagerFactory.php', + 'OCP\\Comments\\IllegalIDChangeException' => __DIR__ . '/../../..' . '/lib/public/Comments/IllegalIDChangeException.php', + 'OCP\\Comments\\MessageTooLongException' => __DIR__ . '/../../..' . '/lib/public/Comments/MessageTooLongException.php', + 'OCP\\Comments\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Comments/NotFoundException.php', + 'OCP\\Console\\ConsoleEvent' => __DIR__ . '/../../..' . '/lib/public/Console/ConsoleEvent.php', + 'OCP\\Constants' => __DIR__ . '/../../..' . '/lib/public/Constants.php', + 'OCP\\Contacts\\ContactsMenu\\IAction' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IAction.php', + 'OCP\\Contacts\\ContactsMenu\\IActionFactory' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IActionFactory.php', + 'OCP\\Contacts\\ContactsMenu\\IContactsStore' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IContactsStore.php', + 'OCP\\Contacts\\ContactsMenu\\IEntry' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IEntry.php', + 'OCP\\Contacts\\ContactsMenu\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/ILinkAction.php', + 'OCP\\Contacts\\ContactsMenu\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IProvider.php', + 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', + 'OCP\\Contacts\\IManager' => __DIR__ . '/../../..' . '/lib/public/Contacts/IManager.php', + 'OCP\\DB\\ISchemaWrapper' => __DIR__ . '/../../..' . '/lib/public/DB/ISchemaWrapper.php', + 'OCP\\DB\\QueryBuilder\\ICompositeExpression' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/ICompositeExpression.php', + 'OCP\\DB\\QueryBuilder\\IExpressionBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IExpressionBuilder.php', + 'OCP\\DB\\QueryBuilder\\IFunctionBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IFunctionBuilder.php', + 'OCP\\DB\\QueryBuilder\\ILiteral' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/ILiteral.php', + 'OCP\\DB\\QueryBuilder\\IParameter' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IParameter.php', + 'OCP\\DB\\QueryBuilder\\IQueryBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IQueryBuilder.php', + 'OCP\\DB\\QueryBuilder\\IQueryFunction' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IQueryFunction.php', + 'OCP\\Dashboard\\Exceptions\\DashboardAppNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Exceptions/DashboardAppNotAvailableException.php', + 'OCP\\Dashboard\\IDashboardManager' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IDashboardManager.php', + 'OCP\\Dashboard\\IDashboardWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IDashboardWidget.php', + 'OCP\\Dashboard\\IManager' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IManager.php', + 'OCP\\Dashboard\\IWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IWidget.php', + 'OCP\\Dashboard\\Model\\IWidgetConfig' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/IWidgetConfig.php', + 'OCP\\Dashboard\\Model\\IWidgetRequest' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/IWidgetRequest.php', + 'OCP\\Dashboard\\Model\\WidgetSetting' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetSetting.php', + 'OCP\\Dashboard\\Model\\WidgetSetup' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetSetup.php', + 'OCP\\Dashboard\\Model\\WidgetTemplate' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetTemplate.php', + 'OCP\\Dashboard\\RegisterWidgetEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterWidgetEvent.php', + 'OCP\\Dashboard\\Service\\IEventsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IEventsService.php', + 'OCP\\Dashboard\\Service\\IWidgetsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IWidgetsService.php', + 'OCP\\Defaults' => __DIR__ . '/../../..' . '/lib/public/Defaults.php', + 'OCP\\Diagnostics\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEvent.php', + 'OCP\\Diagnostics\\IEventLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEventLogger.php', + 'OCP\\Diagnostics\\IQuery' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IQuery.php', + 'OCP\\Diagnostics\\IQueryLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IQueryLogger.php', + 'OCP\\DirectEditing\\ACreateEmpty' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ACreateEmpty.php', + 'OCP\\DirectEditing\\ACreateFromTemplate' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ACreateFromTemplate.php', + 'OCP\\DirectEditing\\ATemplate' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/ATemplate.php', + 'OCP\\DirectEditing\\IEditor' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IEditor.php', + 'OCP\\DirectEditing\\IManager' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IManager.php', + 'OCP\\DirectEditing\\IToken' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IToken.php', + 'OCP\\DirectEditing\\RegisterDirectEditorEvent' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php', + 'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => __DIR__ . '/../../..' . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php', + 'OCP\\Encryption\\IEncryptionModule' => __DIR__ . '/../../..' . '/lib/public/Encryption/IEncryptionModule.php', + 'OCP\\Encryption\\IFile' => __DIR__ . '/../../..' . '/lib/public/Encryption/IFile.php', + 'OCP\\Encryption\\IManager' => __DIR__ . '/../../..' . '/lib/public/Encryption/IManager.php', + 'OCP\\Encryption\\Keys\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Encryption/Keys/IStorage.php', + 'OCP\\EventDispatcher\\ABroadcastedEvent' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/ABroadcastedEvent.php', + 'OCP\\EventDispatcher\\Event' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/Event.php', + 'OCP\\EventDispatcher\\GenericEvent' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/GenericEvent.php', + 'OCP\\EventDispatcher\\IEventDispatcher' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/IEventDispatcher.php', + 'OCP\\EventDispatcher\\IEventListener' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/IEventListener.php', + 'OCP\\Federation\\Exceptions\\ActionNotSupportedException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/ActionNotSupportedException.php', + 'OCP\\Federation\\Exceptions\\AuthenticationFailedException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/AuthenticationFailedException.php', + 'OCP\\Federation\\Exceptions\\BadRequestException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/BadRequestException.php', + 'OCP\\Federation\\Exceptions\\ProviderAlreadyExistsException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php', + 'OCP\\Federation\\Exceptions\\ProviderCouldNotAddShareException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php', + 'OCP\\Federation\\Exceptions\\ProviderDoesNotExistsException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php', + 'OCP\\Federation\\ICloudFederationFactory' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudFederationFactory.php', + 'OCP\\Federation\\ICloudFederationNotification' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudFederationNotification.php', + 'OCP\\Federation\\ICloudFederationProvider' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudFederationProvider.php', + 'OCP\\Federation\\ICloudFederationProviderManager' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudFederationProviderManager.php', + 'OCP\\Federation\\ICloudFederationShare' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudFederationShare.php', + 'OCP\\Federation\\ICloudId' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudId.php', + 'OCP\\Federation\\ICloudIdManager' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudIdManager.php', + 'OCP\\Files' => __DIR__ . '/../../..' . '/lib/public/Files.php', + 'OCP\\Files\\AlreadyExistsException' => __DIR__ . '/../../..' . '/lib/public/Files/AlreadyExistsException.php', + 'OCP\\Files\\Cache\\CacheInsertEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/CacheInsertEvent.php', + 'OCP\\Files\\Cache\\CacheUpdateEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/CacheUpdateEvent.php', + 'OCP\\Files\\Cache\\ICache' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/ICache.php', + 'OCP\\Files\\Cache\\ICacheEntry' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/ICacheEntry.php', + 'OCP\\Files\\Cache\\ICacheEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/ICacheEvent.php', + 'OCP\\Files\\Cache\\IPropagator' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/IPropagator.php', + 'OCP\\Files\\Cache\\IScanner' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/IScanner.php', + 'OCP\\Files\\Cache\\IUpdater' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/IUpdater.php', + 'OCP\\Files\\Cache\\IWatcher' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/IWatcher.php', + 'OCP\\Files\\Config\\ICachedMountFileInfo' => __DIR__ . '/../../..' . '/lib/public/Files/Config/ICachedMountFileInfo.php', + 'OCP\\Files\\Config\\ICachedMountInfo' => __DIR__ . '/../../..' . '/lib/public/Files/Config/ICachedMountInfo.php', + 'OCP\\Files\\Config\\IHomeMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IHomeMountProvider.php', + 'OCP\\Files\\Config\\IMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IMountProvider.php', + 'OCP\\Files\\Config\\IMountProviderCollection' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IMountProviderCollection.php', + 'OCP\\Files\\Config\\IRootMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IRootMountProvider.php', + 'OCP\\Files\\Config\\IUserMountCache' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IUserMountCache.php', + 'OCP\\Files\\EmptyFileNameException' => __DIR__ . '/../../..' . '/lib/public/Files/EmptyFileNameException.php', + 'OCP\\Files\\EntityTooLargeException' => __DIR__ . '/../../..' . '/lib/public/Files/EntityTooLargeException.php', + 'OCP\\Files\\Events\\BeforeFileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeFileScannedEvent.php', + 'OCP\\Files\\Events\\BeforeFolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeFolderScannedEvent.php', + 'OCP\\Files\\Events\\FileCacheUpdated' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileCacheUpdated.php', + 'OCP\\Files\\Events\\FileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileScannedEvent.php', + 'OCP\\Files\\Events\\FolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\NodeAddedToCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeAddedToCache.php', + 'OCP\\Files\\Events\\NodeRemovedFromCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeRemovedFromCache.php', + 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', + 'OCP\\Files\\Events\\Node\\AbstractNodesEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/AbstractNodesEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeCopiedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeCopiedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeCreatedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeReadEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeReadEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeWrittenEvent.php', + 'OCP\\Files\\Events\\Node\\NodeCopiedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeCopiedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeCreatedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeDeletedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeRenamedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeWrittenEvent.php', + 'OCP\\Files\\File' => __DIR__ . '/../../..' . '/lib/public/Files/File.php', + 'OCP\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/public/Files/FileInfo.php', + 'OCP\\Files\\FileNameTooLongException' => __DIR__ . '/../../..' . '/lib/public/Files/FileNameTooLongException.php', + 'OCP\\Files\\Folder' => __DIR__ . '/../../..' . '/lib/public/Files/Folder.php', + 'OCP\\Files\\ForbiddenException' => __DIR__ . '/../../..' . '/lib/public/Files/ForbiddenException.php', + 'OCP\\Files\\GenericFileException' => __DIR__ . '/../../..' . '/lib/public/Files/GenericFileException.php', + 'OCP\\Files\\IAppData' => __DIR__ . '/../../..' . '/lib/public/Files/IAppData.php', + 'OCP\\Files\\IHomeStorage' => __DIR__ . '/../../..' . '/lib/public/Files/IHomeStorage.php', + 'OCP\\Files\\IMimeTypeDetector' => __DIR__ . '/../../..' . '/lib/public/Files/IMimeTypeDetector.php', + 'OCP\\Files\\IMimeTypeLoader' => __DIR__ . '/../../..' . '/lib/public/Files/IMimeTypeLoader.php', + 'OCP\\Files\\IRootFolder' => __DIR__ . '/../../..' . '/lib/public/Files/IRootFolder.php', + 'OCP\\Files\\InvalidCharacterInPathException' => __DIR__ . '/../../..' . '/lib/public/Files/InvalidCharacterInPathException.php', + 'OCP\\Files\\InvalidContentException' => __DIR__ . '/../../..' . '/lib/public/Files/InvalidContentException.php', + 'OCP\\Files\\InvalidDirectoryException' => __DIR__ . '/../../..' . '/lib/public/Files/InvalidDirectoryException.php', + 'OCP\\Files\\InvalidPathException' => __DIR__ . '/../../..' . '/lib/public/Files/InvalidPathException.php', + 'OCP\\Files\\LockNotAcquiredException' => __DIR__ . '/../../..' . '/lib/public/Files/LockNotAcquiredException.php', + 'OCP\\Files\\Mount\\IMountManager' => __DIR__ . '/../../..' . '/lib/public/Files/Mount/IMountManager.php', + 'OCP\\Files\\Mount\\IMountPoint' => __DIR__ . '/../../..' . '/lib/public/Files/Mount/IMountPoint.php', + 'OCP\\Files\\Node' => __DIR__ . '/../../..' . '/lib/public/Files/Node.php', + 'OCP\\Files\\NotEnoughSpaceException' => __DIR__ . '/../../..' . '/lib/public/Files/NotEnoughSpaceException.php', + 'OCP\\Files\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Files/NotFoundException.php', + 'OCP\\Files\\NotPermittedException' => __DIR__ . '/../../..' . '/lib/public/Files/NotPermittedException.php', + 'OCP\\Files\\Notify\\IChange' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/IChange.php', + 'OCP\\Files\\Notify\\INotifyHandler' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/INotifyHandler.php', + 'OCP\\Files\\Notify\\IRenameChange' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/IRenameChange.php', + 'OCP\\Files\\ObjectStore\\IObjectStore' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ReservedWordException' => __DIR__ . '/../../..' . '/lib/public/Files/ReservedWordException.php', + 'OCP\\Files\\Search\\ISearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchBinaryOperator.php', + 'OCP\\Files\\Search\\ISearchComparison' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchComparison.php', + 'OCP\\Files\\Search\\ISearchOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchOperator.php', + 'OCP\\Files\\Search\\ISearchOrder' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchOrder.php', + 'OCP\\Files\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchQuery.php', + 'OCP\\Files\\SimpleFS\\ISimpleFile' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFile.php', + 'OCP\\Files\\SimpleFS\\ISimpleFolder' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFolder.php', + 'OCP\\Files\\SimpleFS\\ISimpleRoot' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleRoot.php', + 'OCP\\Files\\SimpleFS\\InMemoryFile' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/InMemoryFile.php', + 'OCP\\Files\\Storage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage.php', + 'OCP\\Files\\StorageAuthException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageAuthException.php', + 'OCP\\Files\\StorageBadConfigException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageBadConfigException.php', + 'OCP\\Files\\StorageConnectionException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageConnectionException.php', + 'OCP\\Files\\StorageInvalidException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageInvalidException.php', + 'OCP\\Files\\StorageNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageNotAvailableException.php', + 'OCP\\Files\\StorageTimeoutException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageTimeoutException.php', + 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', + 'OCP\\Files\\Storage\\ILockingStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/ILockingStorage.php', + 'OCP\\Files\\Storage\\INotifyStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/INotifyStorage.php', + 'OCP\\Files\\Storage\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorage.php', + 'OCP\\Files\\Storage\\IStorageFactory' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorageFactory.php', + 'OCP\\Files\\Storage\\IWriteStreamStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IWriteStreamStorage.php', + 'OCP\\Files\\UnseekableException' => __DIR__ . '/../../..' . '/lib/public/Files/UnseekableException.php', + 'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => __DIR__ . '/../../..' . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php', + 'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php', + 'OCP\\FullTextSearch\\IFullTextSearchManager' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/IFullTextSearchManager.php', + 'OCP\\FullTextSearch\\IFullTextSearchPlatform' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/IFullTextSearchPlatform.php', + 'OCP\\FullTextSearch\\IFullTextSearchProvider' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/IFullTextSearchProvider.php', + 'OCP\\FullTextSearch\\Model\\IDocumentAccess' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/IDocumentAccess.php', + 'OCP\\FullTextSearch\\Model\\IIndex' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/IIndex.php', + 'OCP\\FullTextSearch\\Model\\IIndexDocument' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/IIndexDocument.php', + 'OCP\\FullTextSearch\\Model\\IIndexOptions' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/IIndexOptions.php', + 'OCP\\FullTextSearch\\Model\\IRunner' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/IRunner.php', + 'OCP\\FullTextSearch\\Model\\ISearchOption' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/ISearchOption.php', + 'OCP\\FullTextSearch\\Model\\ISearchRequest' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/ISearchRequest.php', + 'OCP\\FullTextSearch\\Model\\ISearchRequestSimpleQuery' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/ISearchRequestSimpleQuery.php', + 'OCP\\FullTextSearch\\Model\\ISearchResult' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/ISearchResult.php', + 'OCP\\FullTextSearch\\Model\\ISearchTemplate' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Model/ISearchTemplate.php', + 'OCP\\FullTextSearch\\Service\\IIndexService' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Service/IIndexService.php', + 'OCP\\FullTextSearch\\Service\\IProviderService' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Service/IProviderService.php', + 'OCP\\FullTextSearch\\Service\\ISearchService' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Service/ISearchService.php', + 'OCP\\GlobalScale\\IConfig' => __DIR__ . '/../../..' . '/lib/public/GlobalScale/IConfig.php', + 'OCP\\GroupInterface' => __DIR__ . '/../../..' . '/lib/public/GroupInterface.php', + 'OCP\\Group\\Backend\\ABackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ABackend.php', + 'OCP\\Group\\Backend\\IAddToGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IAddToGroupBackend.php', + 'OCP\\Group\\Backend\\ICountDisabledInGroup' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICountDisabledInGroup.php', + 'OCP\\Group\\Backend\\ICountUsersBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICountUsersBackend.php', + 'OCP\\Group\\Backend\\ICreateGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICreateGroupBackend.php', + 'OCP\\Group\\Backend\\IDeleteGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IDeleteGroupBackend.php', + 'OCP\\Group\\Backend\\IGetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IGetDisplayNameBackend.php', + 'OCP\\Group\\Backend\\IGroupDetailsBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IGroupDetailsBackend.php', + 'OCP\\Group\\Backend\\IHideFromCollaborationBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IHideFromCollaborationBackend.php', + 'OCP\\Group\\Backend\\IIsAdminBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IIsAdminBackend.php', + 'OCP\\Group\\Backend\\IRemoveFromGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IRemoveFromGroupBackend.php', + 'OCP\\Group\\Backend\\ISetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ISetDisplayNameBackend.php', + 'OCP\\Group\\Events\\BeforeGroupCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/BeforeGroupCreatedEvent.php', + 'OCP\\Group\\Events\\BeforeGroupDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/BeforeGroupDeletedEvent.php', + 'OCP\\Group\\Events\\BeforeUserAddedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/BeforeUserAddedEvent.php', + 'OCP\\Group\\Events\\BeforeUserRemovedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/BeforeUserRemovedEvent.php', + 'OCP\\Group\\Events\\GroupCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/GroupCreatedEvent.php', + 'OCP\\Group\\Events\\GroupDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/GroupDeletedEvent.php', + 'OCP\\Group\\Events\\UserAddedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/UserAddedEvent.php', + 'OCP\\Group\\Events\\UserRemovedEvent' => __DIR__ . '/../../..' . '/lib/public/Group/Events/UserRemovedEvent.php', + 'OCP\\Group\\ISubAdmin' => __DIR__ . '/../../..' . '/lib/public/Group/ISubAdmin.php', + 'OCP\\Http\\Client\\IClient' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IClient.php', + 'OCP\\Http\\Client\\IClientService' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IClientService.php', + 'OCP\\Http\\Client\\IResponse' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IResponse.php', + 'OCP\\Http\\Client\\LocalServerException' => __DIR__ . '/../../..' . '/lib/public/Http/Client/LocalServerException.php', + 'OCP\\IAddressBook' => __DIR__ . '/../../..' . '/lib/public/IAddressBook.php', + 'OCP\\IAppConfig' => __DIR__ . '/../../..' . '/lib/public/IAppConfig.php', + 'OCP\\IAvatar' => __DIR__ . '/../../..' . '/lib/public/IAvatar.php', + 'OCP\\IAvatarManager' => __DIR__ . '/../../..' . '/lib/public/IAvatarManager.php', + 'OCP\\ICache' => __DIR__ . '/../../..' . '/lib/public/ICache.php', + 'OCP\\ICacheFactory' => __DIR__ . '/../../..' . '/lib/public/ICacheFactory.php', + 'OCP\\ICertificate' => __DIR__ . '/../../..' . '/lib/public/ICertificate.php', + 'OCP\\ICertificateManager' => __DIR__ . '/../../..' . '/lib/public/ICertificateManager.php', + 'OCP\\IConfig' => __DIR__ . '/../../..' . '/lib/public/IConfig.php', + 'OCP\\IContainer' => __DIR__ . '/../../..' . '/lib/public/IContainer.php', + 'OCP\\IDBConnection' => __DIR__ . '/../../..' . '/lib/public/IDBConnection.php', + 'OCP\\IDateTimeFormatter' => __DIR__ . '/../../..' . '/lib/public/IDateTimeFormatter.php', + 'OCP\\IDateTimeZone' => __DIR__ . '/../../..' . '/lib/public/IDateTimeZone.php', + 'OCP\\IEventSource' => __DIR__ . '/../../..' . '/lib/public/IEventSource.php', + 'OCP\\IGroup' => __DIR__ . '/../../..' . '/lib/public/IGroup.php', + 'OCP\\IGroupManager' => __DIR__ . '/../../..' . '/lib/public/IGroupManager.php', + 'OCP\\IImage' => __DIR__ . '/../../..' . '/lib/public/IImage.php', + 'OCP\\IInitialStateService' => __DIR__ . '/../../..' . '/lib/public/IInitialStateService.php', + 'OCP\\IL10N' => __DIR__ . '/../../..' . '/lib/public/IL10N.php', + 'OCP\\ILogger' => __DIR__ . '/../../..' . '/lib/public/ILogger.php', + 'OCP\\IMemcache' => __DIR__ . '/../../..' . '/lib/public/IMemcache.php', + 'OCP\\IMemcacheTTL' => __DIR__ . '/../../..' . '/lib/public/IMemcacheTTL.php', + 'OCP\\INavigationManager' => __DIR__ . '/../../..' . '/lib/public/INavigationManager.php', + 'OCP\\IPreview' => __DIR__ . '/../../..' . '/lib/public/IPreview.php', + 'OCP\\IRequest' => __DIR__ . '/../../..' . '/lib/public/IRequest.php', + 'OCP\\ISearch' => __DIR__ . '/../../..' . '/lib/public/ISearch.php', + 'OCP\\IServerContainer' => __DIR__ . '/../../..' . '/lib/public/IServerContainer.php', + 'OCP\\ISession' => __DIR__ . '/../../..' . '/lib/public/ISession.php', + 'OCP\\ITagManager' => __DIR__ . '/../../..' . '/lib/public/ITagManager.php', + 'OCP\\ITags' => __DIR__ . '/../../..' . '/lib/public/ITags.php', + 'OCP\\ITempManager' => __DIR__ . '/../../..' . '/lib/public/ITempManager.php', + 'OCP\\IURLGenerator' => __DIR__ . '/../../..' . '/lib/public/IURLGenerator.php', + 'OCP\\IUser' => __DIR__ . '/../../..' . '/lib/public/IUser.php', + 'OCP\\IUserBackend' => __DIR__ . '/../../..' . '/lib/public/IUserBackend.php', + 'OCP\\IUserManager' => __DIR__ . '/../../..' . '/lib/public/IUserManager.php', + 'OCP\\IUserSession' => __DIR__ . '/../../..' . '/lib/public/IUserSession.php', + 'OCP\\Image' => __DIR__ . '/../../..' . '/lib/public/Image.php', + 'OCP\\L10N\\IFactory' => __DIR__ . '/../../..' . '/lib/public/L10N/IFactory.php', + 'OCP\\L10N\\ILanguageIterator' => __DIR__ . '/../../..' . '/lib/public/L10N/ILanguageIterator.php', + 'OCP\\LDAP\\IDeletionFlagSupport' => __DIR__ . '/../../..' . '/lib/public/LDAP/IDeletionFlagSupport.php', + 'OCP\\LDAP\\ILDAPProvider' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProvider.php', + 'OCP\\LDAP\\ILDAPProviderFactory' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProviderFactory.php', + 'OCP\\Lock\\ILockingProvider' => __DIR__ . '/../../..' . '/lib/public/Lock/ILockingProvider.php', + 'OCP\\Lock\\LockedException' => __DIR__ . '/../../..' . '/lib/public/Lock/LockedException.php', + 'OCP\\Lock\\ManuallyLockedException' => __DIR__ . '/../../..' . '/lib/public/Lock/ManuallyLockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => __DIR__ . '/../../..' . '/lib/public/Lockdown/ILockdownManager.php', + 'OCP\\Log\\IDataLogger' => __DIR__ . '/../../..' . '/lib/public/Log/IDataLogger.php', + 'OCP\\Log\\IFileBased' => __DIR__ . '/../../..' . '/lib/public/Log/IFileBased.php', + 'OCP\\Log\\ILogFactory' => __DIR__ . '/../../..' . '/lib/public/Log/ILogFactory.php', + 'OCP\\Log\\IWriter' => __DIR__ . '/../../..' . '/lib/public/Log/IWriter.php', + 'OCP\\Log\\RotationTrait' => __DIR__ . '/../../..' . '/lib/public/Log/RotationTrait.php', + 'OCP\\Mail\\Events\\BeforeMessageSent' => __DIR__ . '/../../..' . '/lib/public/Mail/Events/BeforeMessageSent.php', + 'OCP\\Mail\\IAttachment' => __DIR__ . '/../../..' . '/lib/public/Mail/IAttachment.php', + 'OCP\\Mail\\IEMailTemplate' => __DIR__ . '/../../..' . '/lib/public/Mail/IEMailTemplate.php', + 'OCP\\Mail\\IMailer' => __DIR__ . '/../../..' . '/lib/public/Mail/IMailer.php', + 'OCP\\Mail\\IMessage' => __DIR__ . '/../../..' . '/lib/public/Mail/IMessage.php', + 'OCP\\Migration\\BigIntMigration' => __DIR__ . '/../../..' . '/lib/public/Migration/BigIntMigration.php', + 'OCP\\Migration\\IMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IMigrationStep.php', + 'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php', + 'OCP\\Migration\\IRepairStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IRepairStep.php', + 'OCP\\Migration\\SimpleMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/SimpleMigrationStep.php', + 'OCP\\Notification\\AlreadyProcessedException' => __DIR__ . '/../../..' . '/lib/public/Notification/AlreadyProcessedException.php', + 'OCP\\Notification\\IAction' => __DIR__ . '/../../..' . '/lib/public/Notification/IAction.php', + 'OCP\\Notification\\IApp' => __DIR__ . '/../../..' . '/lib/public/Notification/IApp.php', + 'OCP\\Notification\\IDeferrableApp' => __DIR__ . '/../../..' . '/lib/public/Notification/IDeferrableApp.php', + 'OCP\\Notification\\IDismissableNotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/IDismissableNotifier.php', + 'OCP\\Notification\\IManager' => __DIR__ . '/../../..' . '/lib/public/Notification/IManager.php', + 'OCP\\Notification\\INotification' => __DIR__ . '/../../..' . '/lib/public/Notification/INotification.php', + 'OCP\\Notification\\INotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/INotifier.php', + 'OCP\\OCS\\IDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCS/IDiscoveryService.php', + 'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php', + 'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php', + 'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php', + 'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php', + 'OCP\\Remote\\Api\\IApiCollection' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiCollection.php', + 'OCP\\Remote\\Api\\IApiFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiFactory.php', + 'OCP\\Remote\\Api\\ICapabilitiesApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/ICapabilitiesApi.php', + 'OCP\\Remote\\Api\\IUserApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IUserApi.php', + 'OCP\\Remote\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Remote/ICredentials.php', + 'OCP\\Remote\\IInstance' => __DIR__ . '/../../..' . '/lib/public/Remote/IInstance.php', + 'OCP\\Remote\\IInstanceFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/IInstanceFactory.php', + 'OCP\\Remote\\IUser' => __DIR__ . '/../../..' . '/lib/public/Remote/IUser.php', + 'OCP\\RichObjectStrings\\Definitions' => __DIR__ . '/../../..' . '/lib/public/RichObjectStrings/Definitions.php', + 'OCP\\RichObjectStrings\\IValidator' => __DIR__ . '/../../..' . '/lib/public/RichObjectStrings/IValidator.php', + 'OCP\\RichObjectStrings\\InvalidObjectExeption' => __DIR__ . '/../../..' . '/lib/public/RichObjectStrings/InvalidObjectExeption.php', + 'OCP\\Route\\IRoute' => __DIR__ . '/../../..' . '/lib/public/Route/IRoute.php', + 'OCP\\Route\\IRouter' => __DIR__ . '/../../..' . '/lib/public/Route/IRouter.php', + 'OCP\\SabrePluginEvent' => __DIR__ . '/../../..' . '/lib/public/SabrePluginEvent.php', + 'OCP\\SabrePluginException' => __DIR__ . '/../../..' . '/lib/public/SabrePluginException.php', + 'OCP\\Search\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IProvider.php', + 'OCP\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Search/ISearchQuery.php', + 'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php', + 'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php', + 'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php', + 'OCP\\Search\\SearchResult' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResult.php', + 'OCP\\Search\\SearchResultEntry' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResultEntry.php', + 'OCP\\Security\\Bruteforce\\MaxDelayReached' => __DIR__ . '/../../..' . '/lib/public/Security/Bruteforce/MaxDelayReached.php', + 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', + 'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', + 'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php', + 'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', + 'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php', + 'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php', + 'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php', + 'OCP\\Security\\IHasher' => __DIR__ . '/../../..' . '/lib/public/Security/IHasher.php', + 'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php', + 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', + 'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', + 'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', + 'OCP\\Settings\\ISection' => __DIR__ . '/../../..' . '/lib/public/Settings/ISection.php', + 'OCP\\Settings\\ISettings' => __DIR__ . '/../../..' . '/lib/public/Settings/ISettings.php', + 'OCP\\Settings\\ISubAdminSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/ISubAdminSettings.php', + 'OCP\\Share' => __DIR__ . '/../../..' . '/lib/public/Share.php', + 'OCP\\Share\\Events\\ShareCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareCreatedEvent.php', + 'OCP\\Share\\Events\\VerifyMountPointEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/VerifyMountPointEvent.php', + 'OCP\\Share\\Exceptions\\GenericShareException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/GenericShareException.php', + 'OCP\\Share\\Exceptions\\IllegalIDChangeException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/IllegalIDChangeException.php', + 'OCP\\Share\\Exceptions\\ShareNotFound' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/ShareNotFound.php', + 'OCP\\Share\\IManager' => __DIR__ . '/../../..' . '/lib/public/Share/IManager.php', + 'OCP\\Share\\IProviderFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IProviderFactory.php', + 'OCP\\Share\\IShare' => __DIR__ . '/../../..' . '/lib/public/Share/IShare.php', + 'OCP\\Share\\IShareHelper' => __DIR__ . '/../../..' . '/lib/public/Share/IShareHelper.php', + 'OCP\\Share\\IShareProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProvider.php', + 'OCP\\Share_Backend' => __DIR__ . '/../../..' . '/lib/public/Share_Backend.php', + 'OCP\\Share_Backend_Collection' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_Collection.php', + 'OCP\\Share_Backend_File_Dependent' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_File_Dependent.php', + 'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php', + 'OCP\\Support\\CrashReport\\IMessageReporter' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IMessageReporter.php', + 'OCP\\Support\\CrashReport\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IRegistry.php', + 'OCP\\Support\\CrashReport\\IReporter' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IReporter.php', + 'OCP\\Support\\Subscription\\Exception\\AlreadyRegisteredException' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/Exception/AlreadyRegisteredException.php', + 'OCP\\Support\\Subscription\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/IRegistry.php', + 'OCP\\Support\\Subscription\\ISubscription' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/ISubscription.php', + 'OCP\\Support\\Subscription\\ISupportedApps' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/ISupportedApps.php', + 'OCP\\SystemTag\\ISystemTag' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTag.php', + 'OCP\\SystemTag\\ISystemTagManager' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTagManager.php', + 'OCP\\SystemTag\\ISystemTagManagerFactory' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTagManagerFactory.php', + 'OCP\\SystemTag\\ISystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTagObjectMapper.php', + 'OCP\\SystemTag\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ManagerEvent.php', + 'OCP\\SystemTag\\MapperEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/MapperEvent.php', + 'OCP\\SystemTag\\SystemTagsEntityEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/SystemTagsEntityEvent.php', + 'OCP\\SystemTag\\TagAlreadyExistsException' => __DIR__ . '/../../..' . '/lib/public/SystemTag/TagAlreadyExistsException.php', + 'OCP\\SystemTag\\TagNotFoundException' => __DIR__ . '/../../..' . '/lib/public/SystemTag/TagNotFoundException.php', + 'OCP\\Template' => __DIR__ . '/../../..' . '/lib/public/Template.php', + 'OCP\\User' => __DIR__ . '/../../..' . '/lib/public/User.php', + 'OCP\\UserInterface' => __DIR__ . '/../../..' . '/lib/public/UserInterface.php', + 'OCP\\UserStatus\\IManager' => __DIR__ . '/../../..' . '/lib/public/UserStatus/IManager.php', + 'OCP\\UserStatus\\IProvider' => __DIR__ . '/../../..' . '/lib/public/UserStatus/IProvider.php', + 'OCP\\UserStatus\\IUserStatus' => __DIR__ . '/../../..' . '/lib/public/UserStatus/IUserStatus.php', + 'OCP\\User\\Backend\\ABackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ABackend.php', + 'OCP\\User\\Backend\\ICheckPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ICheckPasswordBackend.php', + 'OCP\\User\\Backend\\ICountUsersBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ICountUsersBackend.php', + 'OCP\\User\\Backend\\ICreateUserBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ICreateUserBackend.php', + 'OCP\\User\\Backend\\ICustomLogout' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ICustomLogout.php', + 'OCP\\User\\Backend\\IGetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetDisplayNameBackend.php', + 'OCP\\User\\Backend\\IGetHomeBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetHomeBackend.php', + 'OCP\\User\\Backend\\IGetRealUIDBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetRealUIDBackend.php', + 'OCP\\User\\Backend\\IPasswordConfirmationBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordConfirmationBackend.php', + 'OCP\\User\\Backend\\IProvideAvatarBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideAvatarBackend.php', + 'OCP\\User\\Backend\\ISetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetDisplayNameBackend.php', + 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', + 'OCP\\User\\Events\\BeforePasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforePasswordUpdatedEvent.php', + 'OCP\\User\\Events\\BeforeUserCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserCreatedEvent.php', + 'OCP\\User\\Events\\BeforeUserDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserDeletedEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php', + 'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php', + 'OCP\\User\\Events\\CreateUserEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/CreateUserEvent.php', + 'OCP\\User\\Events\\PasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PasswordUpdatedEvent.php', + 'OCP\\User\\Events\\PostLoginEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PostLoginEvent.php', + 'OCP\\User\\Events\\UserChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserChangedEvent.php', + 'OCP\\User\\Events\\UserCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserCreatedEvent.php', + 'OCP\\User\\Events\\UserDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserDeletedEvent.php', + 'OCP\\User\\Events\\UserLiveStatusEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLiveStatusEvent.php', + 'OCP\\User\\Events\\UserLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedInEvent.php', + 'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php', + 'OCP\\User\\Events\\UserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedOutEvent.php', + 'OCP\\User\\GetQuotaEvent' => __DIR__ . '/../../..' . '/lib/public/User/GetQuotaEvent.php', + 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', + 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', + 'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', + 'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php', + 'OCP\\WorkflowEngine\\EntityContext\\IIcon' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IIcon.php', + 'OCP\\WorkflowEngine\\EntityContext\\IUrl' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IUrl.php', + 'OCP\\WorkflowEngine\\Events\\LoadSettingsScriptsEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/Events/LoadSettingsScriptsEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterChecksEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/Events/RegisterChecksEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterEntitiesEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/Events/RegisterEntitiesEvent.php', + 'OCP\\WorkflowEngine\\Events\\RegisterOperationsEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/Events/RegisterOperationsEvent.php', + 'OCP\\WorkflowEngine\\GenericEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/GenericEntityEvent.php', + 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IComplexOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperation.php', + 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', + 'OCP\\WorkflowEngine\\IEntityCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityCheck.php', + 'OCP\\WorkflowEngine\\IEntityCompat' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityCompat.php', + 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', + 'OCP\\WorkflowEngine\\IFileCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IFileCheck.php', + 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', + 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IOperationCompat' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperationCompat.php', + 'OCP\\WorkflowEngine\\IRuleMatcher' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IRuleMatcher.php', + 'OCP\\WorkflowEngine\\ISpecificOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperation.php', + 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', + 'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php', + 'OC\\Accounts\\AccountProperty' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountProperty.php', + 'OC\\Accounts\\Hooks' => __DIR__ . '/../../..' . '/lib/private/Accounts/Hooks.php', + 'OC\\Activity\\ActivitySettingsAdapter' => __DIR__ . '/../../..' . '/lib/private/Activity/ActivitySettingsAdapter.php', + 'OC\\Activity\\Event' => __DIR__ . '/../../..' . '/lib/private/Activity/Event.php', + 'OC\\Activity\\EventMerger' => __DIR__ . '/../../..' . '/lib/private/Activity/EventMerger.php', + 'OC\\Activity\\Manager' => __DIR__ . '/../../..' . '/lib/private/Activity/Manager.php', + 'OC\\AllConfig' => __DIR__ . '/../../..' . '/lib/private/AllConfig.php', + 'OC\\AppConfig' => __DIR__ . '/../../..' . '/lib/private/AppConfig.php', + 'OC\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/private/AppFramework/App.php', + 'OC\\AppFramework\\Bootstrap\\BootContext' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/BootContext.php', + 'OC\\AppFramework\\Bootstrap\\Coordinator' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/Coordinator.php', + 'OC\\AppFramework\\Bootstrap\\FunctionInjector' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/FunctionInjector.php', + 'OC\\AppFramework\\Bootstrap\\RegistrationContext' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/RegistrationContext.php', + 'OC\\AppFramework\\DependencyInjection\\DIContainer' => __DIR__ . '/../../..' . '/lib/private/AppFramework/DependencyInjection/DIContainer.php', + 'OC\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http.php', + 'OC\\AppFramework\\Http\\Dispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Dispatcher.php', + 'OC\\AppFramework\\Http\\Output' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Output.php', + 'OC\\AppFramework\\Http\\Request' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Request.php', + 'OC\\AppFramework\\Logger' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Logger.php', + 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', + 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', + 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', + 'OC\\AppFramework\\Middleware\\NotModifiedMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php', + 'OC\\AppFramework\\Middleware\\OCSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\CSPMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\CrossSiteRequestForgeryException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\LaxSameSiteCookieFailedException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotAdminException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotConfirmedException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\ReloadExecutionException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php', + 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php', + 'OC\\AppFramework\\Middleware\\Security\\FeaturePolicyMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php', + 'OC\\AppFramework\\Middleware\\Security\\SecurityMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php', + 'OC\\AppFramework\\Middleware\\SessionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/SessionMiddleware.php', + 'OC\\AppFramework\\OCS\\BaseResponse' => __DIR__ . '/../../..' . '/lib/private/AppFramework/OCS/BaseResponse.php', + 'OC\\AppFramework\\OCS\\V1Response' => __DIR__ . '/../../..' . '/lib/private/AppFramework/OCS/V1Response.php', + 'OC\\AppFramework\\OCS\\V2Response' => __DIR__ . '/../../..' . '/lib/private/AppFramework/OCS/V2Response.php', + 'OC\\AppFramework\\Routing\\RouteActionHandler' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Routing/RouteActionHandler.php', + 'OC\\AppFramework\\Routing\\RouteConfig' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Routing/RouteConfig.php', + 'OC\\AppFramework\\ScopedPsrLogger' => __DIR__ . '/../../..' . '/lib/private/AppFramework/ScopedPsrLogger.php', + 'OC\\AppFramework\\Services\\AppConfig' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Services/AppConfig.php', + 'OC\\AppFramework\\Services\\InitialState' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Services/InitialState.php', + 'OC\\AppFramework\\Utility\\ControllerMethodReflector' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/ControllerMethodReflector.php', + 'OC\\AppFramework\\Utility\\SimpleContainer' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/SimpleContainer.php', + 'OC\\AppFramework\\Utility\\TimeFactory' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/TimeFactory.php', + 'OC\\App\\AppManager' => __DIR__ . '/../../..' . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Bundles\\Bundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/Bundle.php', + 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/BundleFetcher.php', + 'OC\\App\\AppStore\\Bundles\\CoreBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/CoreBundle.php', + 'OC\\App\\AppStore\\Bundles\\EducationBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/EducationBundle.php', + 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php', + 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php', + 'OC\\App\\AppStore\\Bundles\\HubBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/HubBundle.php', + 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php', + 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\Fetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/Fetcher.php', + 'OC\\App\\AppStore\\Version\\Version' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Version/Version.php', + 'OC\\App\\AppStore\\Version\\VersionParser' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Version/VersionParser.php', + 'OC\\App\\CodeChecker\\AbstractCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/AbstractCheck.php', + 'OC\\App\\CodeChecker\\CodeChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/CodeChecker.php', + 'OC\\App\\CodeChecker\\DatabaseSchemaChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/DatabaseSchemaChecker.php', + 'OC\\App\\CodeChecker\\DeprecationCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/DeprecationCheck.php', + 'OC\\App\\CodeChecker\\EmptyCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/EmptyCheck.php', + 'OC\\App\\CodeChecker\\ICheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/ICheck.php', + 'OC\\App\\CodeChecker\\InfoChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/InfoChecker.php', + 'OC\\App\\CodeChecker\\LanguageParseChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/LanguageParseChecker.php', + 'OC\\App\\CodeChecker\\MigrationSchemaChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/MigrationSchemaChecker.php', + 'OC\\App\\CodeChecker\\NodeVisitor' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/NodeVisitor.php', + 'OC\\App\\CodeChecker\\PrivateCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/PrivateCheck.php', + 'OC\\App\\CodeChecker\\StrongComparisonCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/StrongComparisonCheck.php', + 'OC\\App\\CompareVersion' => __DIR__ . '/../../..' . '/lib/private/App/CompareVersion.php', + 'OC\\App\\DependencyAnalyzer' => __DIR__ . '/../../..' . '/lib/private/App/DependencyAnalyzer.php', + 'OC\\App\\InfoParser' => __DIR__ . '/../../..' . '/lib/private/App/InfoParser.php', + 'OC\\App\\Platform' => __DIR__ . '/../../..' . '/lib/private/App/Platform.php', + 'OC\\App\\PlatformRepository' => __DIR__ . '/../../..' . '/lib/private/App/PlatformRepository.php', + 'OC\\Archive\\Archive' => __DIR__ . '/../../..' . '/lib/private/Archive/Archive.php', + 'OC\\Archive\\TAR' => __DIR__ . '/../../..' . '/lib/private/Archive/TAR.php', + 'OC\\Archive\\ZIP' => __DIR__ . '/../../..' . '/lib/private/Archive/ZIP.php', + 'OC\\Authentication\\Events\\ARemoteWipeEvent' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/ARemoteWipeEvent.php', + 'OC\\Authentication\\Events\\LoginFailed' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/LoginFailed.php', + 'OC\\Authentication\\Events\\RemoteWipeFinished' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeFinished.php', + 'OC\\Authentication\\Events\\RemoteWipeStarted' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeStarted.php', + 'OC\\Authentication\\Exceptions\\ExpiredTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/ExpiredTokenException.php', + 'OC\\Authentication\\Exceptions\\InvalidProviderException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/InvalidProviderException.php', + 'OC\\Authentication\\Exceptions\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/InvalidTokenException.php', + 'OC\\Authentication\\Exceptions\\LoginRequiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/LoginRequiredException.php', + 'OC\\Authentication\\Exceptions\\PasswordLoginForbiddenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php', + 'OC\\Authentication\\Exceptions\\PasswordlessTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/PasswordlessTokenException.php', + 'OC\\Authentication\\Exceptions\\TokenPasswordExpiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php', + 'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php', + 'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php', + 'OC\\Authentication\\Exceptions\\WipeTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/WipeTokenException.php', + 'OC\\Authentication\\Listeners\\LoginFailedListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/LoginFailedListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeActivityListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php', + 'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php', + 'OC\\Authentication\\Listeners\\UserDeletedStoreCleanupListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php', + 'OC\\Authentication\\Listeners\\UserDeletedTokenCleanupListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php', + 'OC\\Authentication\\Listeners\\UserLoggedInListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/UserLoggedInListener.php', + 'OC\\Authentication\\LoginCredentials\\Credentials' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Credentials.php', + 'OC\\Authentication\\LoginCredentials\\Store' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Store.php', + 'OC\\Authentication\\Login\\ALoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ALoginCommand.php', + 'OC\\Authentication\\Login\\Chain' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/Chain.php', + 'OC\\Authentication\\Login\\ClearLostPasswordTokensCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php', + 'OC\\Authentication\\Login\\CompleteLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CompleteLoginCommand.php', + 'OC\\Authentication\\Login\\CreateSessionTokenCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/CreateSessionTokenCommand.php', + 'OC\\Authentication\\Login\\EmailLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/EmailLoginCommand.php', + 'OC\\Authentication\\Login\\FinishRememberedLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/FinishRememberedLoginCommand.php', + 'OC\\Authentication\\Login\\LoggedInCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoggedInCheckCommand.php', + 'OC\\Authentication\\Login\\LoginData' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginData.php', + 'OC\\Authentication\\Login\\LoginResult' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/LoginResult.php', + 'OC\\Authentication\\Login\\PreLoginHookCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/PreLoginHookCommand.php', + 'OC\\Authentication\\Login\\SetUserTimezoneCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/SetUserTimezoneCommand.php', + 'OC\\Authentication\\Login\\TwoFactorCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/TwoFactorCommand.php', + 'OC\\Authentication\\Login\\UidLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UidLoginCommand.php', + 'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php', + 'OC\\Authentication\\Login\\UserDisabledCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php', + 'OC\\Authentication\\Login\\WebAuthnChain' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/WebAuthnChain.php', + 'OC\\Authentication\\Login\\WebAuthnLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/WebAuthnLoginCommand.php', + 'OC\\Authentication\\Notifications\\Notifier' => __DIR__ . '/../../..' . '/lib/private/Authentication/Notifications/Notifier.php', + 'OC\\Authentication\\Token\\DefaultToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultToken.php', + 'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php', + 'OC\\Authentication\\Token\\DefaultTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenMapper.php', + 'OC\\Authentication\\Token\\DefaultTokenProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenProvider.php', + 'OC\\Authentication\\Token\\INamedToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/INamedToken.php', + 'OC\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IProvider.php', + 'OC\\Authentication\\Token\\IToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IToken.php', + 'OC\\Authentication\\Token\\IWipeableToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IWipeableToken.php', + 'OC\\Authentication\\Token\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/Manager.php', + 'OC\\Authentication\\Token\\PublicKeyToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyToken.php', + 'OC\\Authentication\\Token\\PublicKeyTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php', + 'OC\\Authentication\\Token\\PublicKeyTokenProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php', + 'OC\\Authentication\\Token\\RemoteWipe' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/RemoteWipe.php', + 'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php', + 'OC\\Authentication\\TwoFactorAuth\\EnforcementState' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php', + 'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php', + 'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php', + 'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', + 'OC\\Authentication\\TwoFactorAuth\\Registry' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Registry.php', + 'OC\\Authentication\\WebAuthn\\CredentialRepository' => __DIR__ . '/../../..' . '/lib/private/Authentication/WebAuthn/CredentialRepository.php', + 'OC\\Authentication\\WebAuthn\\Db\\PublicKeyCredentialEntity' => __DIR__ . '/../../..' . '/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php', + 'OC\\Authentication\\WebAuthn\\Db\\PublicKeyCredentialMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php', + 'OC\\Authentication\\WebAuthn\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/WebAuthn/Manager.php', + 'OC\\Avatar\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/Avatar.php', + 'OC\\Avatar\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/Avatar/AvatarManager.php', + 'OC\\Avatar\\GuestAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/GuestAvatar.php', + 'OC\\Avatar\\UserAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/UserAvatar.php', + 'OC\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Job.php', + 'OC\\BackgroundJob\\JobList' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/JobList.php', + 'OC\\BackgroundJob\\Legacy\\QueuedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Legacy/QueuedJob.php', + 'OC\\BackgroundJob\\Legacy\\RegularJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Legacy/RegularJob.php', + 'OC\\BackgroundJob\\QueuedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/QueuedJob.php', + 'OC\\BackgroundJob\\TimedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/TimedJob.php', + 'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php', + 'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php', + 'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php', + 'OC\\Calendar\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Manager.php', + 'OC\\Calendar\\Resource\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Resource/Manager.php', + 'OC\\Calendar\\Room\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Room/Manager.php', + 'OC\\CapabilitiesManager' => __DIR__ . '/../../..' . '/lib/private/CapabilitiesManager.php', + 'OC\\Collaboration\\AutoComplete\\Manager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/AutoComplete/Manager.php', + 'OC\\Collaboration\\Collaborators\\GroupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/GroupPlugin.php', + 'OC\\Collaboration\\Collaborators\\LookupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/LookupPlugin.php', + 'OC\\Collaboration\\Collaborators\\MailPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/MailPlugin.php', + 'OC\\Collaboration\\Collaborators\\RemoteGroupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php', + 'OC\\Collaboration\\Collaborators\\RemotePlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/RemotePlugin.php', + 'OC\\Collaboration\\Collaborators\\Search' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/Search.php', + 'OC\\Collaboration\\Collaborators\\SearchResult' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/SearchResult.php', + 'OC\\Collaboration\\Collaborators\\UserPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserPlugin.php', + 'OC\\Collaboration\\Resources\\Collection' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Collection.php', + 'OC\\Collaboration\\Resources\\Listener' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Listener.php', + 'OC\\Collaboration\\Resources\\Manager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Manager.php', + 'OC\\Collaboration\\Resources\\ProviderManager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/ProviderManager.php', + 'OC\\Collaboration\\Resources\\Resource' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Resource.php', + 'OC\\Color' => __DIR__ . '/../../..' . '/lib/private/Color.php', + 'OC\\Command\\AsyncBus' => __DIR__ . '/../../..' . '/lib/private/Command/AsyncBus.php', + 'OC\\Command\\CallableJob' => __DIR__ . '/../../..' . '/lib/private/Command/CallableJob.php', + 'OC\\Command\\ClosureJob' => __DIR__ . '/../../..' . '/lib/private/Command/ClosureJob.php', + 'OC\\Command\\CommandJob' => __DIR__ . '/../../..' . '/lib/private/Command/CommandJob.php', + 'OC\\Command\\CronBus' => __DIR__ . '/../../..' . '/lib/private/Command/CronBus.php', + 'OC\\Command\\FileAccess' => __DIR__ . '/../../..' . '/lib/private/Command/FileAccess.php', + 'OC\\Command\\QueueBus' => __DIR__ . '/../../..' . '/lib/private/Command/QueueBus.php', + 'OC\\Comments\\Comment' => __DIR__ . '/../../..' . '/lib/private/Comments/Comment.php', + 'OC\\Comments\\Manager' => __DIR__ . '/../../..' . '/lib/private/Comments/Manager.php', + 'OC\\Comments\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/Comments/ManagerFactory.php', + 'OC\\Config' => __DIR__ . '/../../..' . '/lib/private/Config.php', + 'OC\\Console\\Application' => __DIR__ . '/../../..' . '/lib/private/Console/Application.php', + 'OC\\Console\\TimestampFormatter' => __DIR__ . '/../../..' . '/lib/private/Console/TimestampFormatter.php', + 'OC\\ContactsManager' => __DIR__ . '/../../..' . '/lib/private/ContactsManager.php', + 'OC\\Contacts\\ContactsMenu\\ActionFactory' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/ActionFactory.php', + 'OC\\Contacts\\ContactsMenu\\ActionProviderStore' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/ActionProviderStore.php', + 'OC\\Contacts\\ContactsMenu\\Actions\\LinkAction' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php', + 'OC\\Contacts\\ContactsMenu\\ContactsStore' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/ContactsStore.php', + 'OC\\Contacts\\ContactsMenu\\Entry' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Entry.php', + 'OC\\Contacts\\ContactsMenu\\Manager' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Manager.php', + 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', + 'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php', + 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', + 'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CleanupLoginFlowV2.php', + 'OC\\Core\\Command\\App\\CheckCode' => __DIR__ . '/../../..' . '/core/Command/App/CheckCode.php', + 'OC\\Core\\Command\\App\\Disable' => __DIR__ . '/../../..' . '/core/Command/App/Disable.php', + 'OC\\Core\\Command\\App\\Enable' => __DIR__ . '/../../..' . '/core/Command/App/Enable.php', + 'OC\\Core\\Command\\App\\GetPath' => __DIR__ . '/../../..' . '/core/Command/App/GetPath.php', + 'OC\\Core\\Command\\App\\Install' => __DIR__ . '/../../..' . '/core/Command/App/Install.php', + 'OC\\Core\\Command\\App\\ListApps' => __DIR__ . '/../../..' . '/core/Command/App/ListApps.php', + 'OC\\Core\\Command\\App\\Remove' => __DIR__ . '/../../..' . '/core/Command/App/Remove.php', + 'OC\\Core\\Command\\App\\Update' => __DIR__ . '/../../..' . '/core/Command/App/Update.php', + 'OC\\Core\\Command\\Background\\Ajax' => __DIR__ . '/../../..' . '/core/Command/Background/Ajax.php', + 'OC\\Core\\Command\\Background\\Base' => __DIR__ . '/../../..' . '/core/Command/Background/Base.php', + 'OC\\Core\\Command\\Background\\Cron' => __DIR__ . '/../../..' . '/core/Command/Background/Cron.php', + 'OC\\Core\\Command\\Background\\WebCron' => __DIR__ . '/../../..' . '/core/Command/Background/WebCron.php', + 'OC\\Core\\Command\\Base' => __DIR__ . '/../../..' . '/core/Command/Base.php', + 'OC\\Core\\Command\\Broadcast\\Test' => __DIR__ . '/../../..' . '/core/Command/Broadcast/Test.php', + 'OC\\Core\\Command\\Check' => __DIR__ . '/../../..' . '/core/Command/Check.php', + 'OC\\Core\\Command\\Config\\App\\Base' => __DIR__ . '/../../..' . '/core/Command/Config/App/Base.php', + 'OC\\Core\\Command\\Config\\App\\DeleteConfig' => __DIR__ . '/../../..' . '/core/Command/Config/App/DeleteConfig.php', + 'OC\\Core\\Command\\Config\\App\\GetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/App/GetConfig.php', + 'OC\\Core\\Command\\Config\\App\\SetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/App/SetConfig.php', + 'OC\\Core\\Command\\Config\\Import' => __DIR__ . '/../../..' . '/core/Command/Config/Import.php', + 'OC\\Core\\Command\\Config\\ListConfigs' => __DIR__ . '/../../..' . '/core/Command/Config/ListConfigs.php', + 'OC\\Core\\Command\\Config\\System\\Base' => __DIR__ . '/../../..' . '/core/Command/Config/System/Base.php', + 'OC\\Core\\Command\\Config\\System\\DeleteConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/DeleteConfig.php', + 'OC\\Core\\Command\\Config\\System\\GetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/GetConfig.php', + 'OC\\Core\\Command\\Config\\System\\SetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/SetConfig.php', + 'OC\\Core\\Command\\Db\\AddMissingColumns' => __DIR__ . '/../../..' . '/core/Command/Db/AddMissingColumns.php', + 'OC\\Core\\Command\\Db\\AddMissingIndices' => __DIR__ . '/../../..' . '/core/Command/Db/AddMissingIndices.php', + 'OC\\Core\\Command\\Db\\ConvertFilecacheBigInt' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertFilecacheBigInt.php', + 'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertMysqlToMB4.php', + 'OC\\Core\\Command\\Db\\ConvertType' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertType.php', + 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/ExecuteCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/GenerateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateFromSchemaFileCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/GenerateFromSchemaFileCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/MigrateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/StatusCommand.php', + 'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ChangeKeyStorageRoot.php', + 'OC\\Core\\Command\\Encryption\\DecryptAll' => __DIR__ . '/../../..' . '/core/Command/Encryption/DecryptAll.php', + 'OC\\Core\\Command\\Encryption\\Disable' => __DIR__ . '/../../..' . '/core/Command/Encryption/Disable.php', + 'OC\\Core\\Command\\Encryption\\Enable' => __DIR__ . '/../../..' . '/core/Command/Encryption/Enable.php', + 'OC\\Core\\Command\\Encryption\\EncryptAll' => __DIR__ . '/../../..' . '/core/Command/Encryption/EncryptAll.php', + 'OC\\Core\\Command\\Encryption\\ListModules' => __DIR__ . '/../../..' . '/core/Command/Encryption/ListModules.php', + 'OC\\Core\\Command\\Encryption\\MigrateKeyStorage' => __DIR__ . '/../../..' . '/core/Command/Encryption/MigrateKeyStorage.php', + 'OC\\Core\\Command\\Encryption\\SetDefaultModule' => __DIR__ . '/../../..' . '/core/Command/Encryption/SetDefaultModule.php', + 'OC\\Core\\Command\\Encryption\\ShowKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ShowKeyStorageRoot.php', + 'OC\\Core\\Command\\Encryption\\Status' => __DIR__ . '/../../..' . '/core/Command/Encryption/Status.php', + 'OC\\Core\\Command\\Group\\Add' => __DIR__ . '/../../..' . '/core/Command/Group/Add.php', + 'OC\\Core\\Command\\Group\\AddUser' => __DIR__ . '/../../..' . '/core/Command/Group/AddUser.php', + 'OC\\Core\\Command\\Group\\Delete' => __DIR__ . '/../../..' . '/core/Command/Group/Delete.php', + 'OC\\Core\\Command\\Group\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/Group/ListCommand.php', + 'OC\\Core\\Command\\Group\\RemoveUser' => __DIR__ . '/../../..' . '/core/Command/Group/RemoveUser.php', + 'OC\\Core\\Command\\Integrity\\CheckApp' => __DIR__ . '/../../..' . '/core/Command/Integrity/CheckApp.php', + 'OC\\Core\\Command\\Integrity\\CheckCore' => __DIR__ . '/../../..' . '/core/Command/Integrity/CheckCore.php', + 'OC\\Core\\Command\\Integrity\\SignApp' => __DIR__ . '/../../..' . '/core/Command/Integrity/SignApp.php', + 'OC\\Core\\Command\\Integrity\\SignCore' => __DIR__ . '/../../..' . '/core/Command/Integrity/SignCore.php', + 'OC\\Core\\Command\\InterruptedException' => __DIR__ . '/../../..' . '/core/Command/InterruptedException.php', + 'OC\\Core\\Command\\L10n\\CreateJs' => __DIR__ . '/../../..' . '/core/Command/L10n/CreateJs.php', + 'OC\\Core\\Command\\Log\\File' => __DIR__ . '/../../..' . '/core/Command/Log/File.php', + 'OC\\Core\\Command\\Log\\Manage' => __DIR__ . '/../../..' . '/core/Command/Log/Manage.php', + 'OC\\Core\\Command\\Maintenance\\DataFingerprint' => __DIR__ . '/../../..' . '/core/Command/Maintenance/DataFingerprint.php', + 'OC\\Core\\Command\\Maintenance\\Install' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Install.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\GenerateMimetypeFileBuilder' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateDB' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/UpdateDB.php', + 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/UpdateJS.php', + 'OC\\Core\\Command\\Maintenance\\Mode' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mode.php', + 'OC\\Core\\Command\\Maintenance\\Repair' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Repair.php', + 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php', + 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php', + 'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php', + 'OC\\Core\\Command\\Security\\ImportCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/ImportCertificate.php', + 'OC\\Core\\Command\\Security\\ListCertificates' => __DIR__ . '/../../..' . '/core/Command/Security/ListCertificates.php', + 'OC\\Core\\Command\\Security\\RemoveCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/RemoveCertificate.php', + 'OC\\Core\\Command\\Security\\ResetBruteforceAttempts' => __DIR__ . '/../../..' . '/core/Command/Security/ResetBruteforceAttempts.php', + 'OC\\Core\\Command\\Status' => __DIR__ . '/../../..' . '/core/Command/Status.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Base' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Base.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Cleanup.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Disable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Enable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enforce' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Enforce.php', + 'OC\\Core\\Command\\TwoFactorAuth\\State' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/State.php', + 'OC\\Core\\Command\\Upgrade' => __DIR__ . '/../../..' . '/core/Command/Upgrade.php', + 'OC\\Core\\Command\\User\\Add' => __DIR__ . '/../../..' . '/core/Command/User/Add.php', + 'OC\\Core\\Command\\User\\Delete' => __DIR__ . '/../../..' . '/core/Command/User/Delete.php', + 'OC\\Core\\Command\\User\\Disable' => __DIR__ . '/../../..' . '/core/Command/User/Disable.php', + 'OC\\Core\\Command\\User\\Enable' => __DIR__ . '/../../..' . '/core/Command/User/Enable.php', + 'OC\\Core\\Command\\User\\Info' => __DIR__ . '/../../..' . '/core/Command/User/Info.php', + 'OC\\Core\\Command\\User\\LastSeen' => __DIR__ . '/../../..' . '/core/Command/User/LastSeen.php', + 'OC\\Core\\Command\\User\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/User/ListCommand.php', + 'OC\\Core\\Command\\User\\Report' => __DIR__ . '/../../..' . '/core/Command/User/Report.php', + 'OC\\Core\\Command\\User\\ResetPassword' => __DIR__ . '/../../..' . '/core/Command/User/ResetPassword.php', + 'OC\\Core\\Command\\User\\Setting' => __DIR__ . '/../../..' . '/core/Command/User/Setting.php', + 'OC\\Core\\Controller\\AppPasswordController' => __DIR__ . '/../../..' . '/core/Controller/AppPasswordController.php', + 'OC\\Core\\Controller\\AutoCompleteController' => __DIR__ . '/../../..' . '/core/Controller/AutoCompleteController.php', + 'OC\\Core\\Controller\\AvatarController' => __DIR__ . '/../../..' . '/core/Controller/AvatarController.php', + 'OC\\Core\\Controller\\CSRFTokenController' => __DIR__ . '/../../..' . '/core/Controller/CSRFTokenController.php', + 'OC\\Core\\Controller\\ClientFlowLoginController' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginController.php', + 'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginV2Controller.php', + 'OC\\Core\\Controller\\CollaborationResourcesController' => __DIR__ . '/../../..' . '/core/Controller/CollaborationResourcesController.php', + 'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php', + 'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php', + 'OC\\Core\\Controller\\GuestAvatarController' => __DIR__ . '/../../..' . '/core/Controller/GuestAvatarController.php', + 'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php', + 'OC\\Core\\Controller\\LoginController' => __DIR__ . '/../../..' . '/core/Controller/LoginController.php', + 'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php', + 'OC\\Core\\Controller\\NavigationController' => __DIR__ . '/../../..' . '/core/Controller/NavigationController.php', + 'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php', + 'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php', + 'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php', + 'OC\\Core\\Controller\\RecommendedAppsController' => __DIR__ . '/../../..' . '/core/Controller/RecommendedAppsController.php', + 'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php', + 'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php', + 'OC\\Core\\Controller\\SvgController' => __DIR__ . '/../../..' . '/core/Controller/SvgController.php', + 'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php', + 'OC\\Core\\Controller\\UnifiedSearchController' => __DIR__ . '/../../..' . '/core/Controller/UnifiedSearchController.php', + 'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php', + 'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php', + 'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php', + 'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php', + 'OC\\Core\\Controller\\WipeController' => __DIR__ . '/../../..' . '/core/Controller/WipeController.php', + 'OC\\Core\\Data\\LoginFlowV2Credentials' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Credentials.php', + 'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php', + 'OC\\Core\\Db\\LoginFlowV2' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2.php', + 'OC\\Core\\Db\\LoginFlowV2Mapper' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2Mapper.php', + 'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => __DIR__ . '/../../..' . '/core/Exception/LoginFlowV2NotFoundException.php', + 'OC\\Core\\Exception\\ResetPasswordException' => __DIR__ . '/../../..' . '/core/Exception/ResetPasswordException.php', + 'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php', + 'OC\\Core\\Migrations\\Version13000Date20170705121758' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170705121758.php', + 'OC\\Core\\Migrations\\Version13000Date20170718121200' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170718121200.php', + 'OC\\Core\\Migrations\\Version13000Date20170814074715' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170814074715.php', + 'OC\\Core\\Migrations\\Version13000Date20170919121250' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170919121250.php', + 'OC\\Core\\Migrations\\Version13000Date20170926101637' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170926101637.php', + 'OC\\Core\\Migrations\\Version14000Date20180129121024' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180129121024.php', + 'OC\\Core\\Migrations\\Version14000Date20180404140050' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180404140050.php', + 'OC\\Core\\Migrations\\Version14000Date20180516101403' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180516101403.php', + 'OC\\Core\\Migrations\\Version14000Date20180518120534' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180518120534.php', + 'OC\\Core\\Migrations\\Version14000Date20180522074438' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180522074438.php', + 'OC\\Core\\Migrations\\Version14000Date20180626223656' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180626223656.php', + 'OC\\Core\\Migrations\\Version14000Date20180710092004' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180710092004.php', + 'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.php', + 'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php', + 'OC\\Core\\Migrations\\Version15000Date20181015062942' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181015062942.php', + 'OC\\Core\\Migrations\\Version15000Date20181029084625' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181029084625.php', + 'OC\\Core\\Migrations\\Version16000Date20190207141427' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190207141427.php', + 'OC\\Core\\Migrations\\Version16000Date20190212081545' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190212081545.php', + 'OC\\Core\\Migrations\\Version16000Date20190427105638' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190427105638.php', + 'OC\\Core\\Migrations\\Version16000Date20190428150708' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190428150708.php', + 'OC\\Core\\Migrations\\Version17000Date20190514105811' => __DIR__ . '/../../..' . '/core/Migrations/Version17000Date20190514105811.php', + 'OC\\Core\\Migrations\\Version18000Date20190920085628' => __DIR__ . '/../../..' . '/core/Migrations/Version18000Date20190920085628.php', + 'OC\\Core\\Migrations\\Version18000Date20191014105105' => __DIR__ . '/../../..' . '/core/Migrations/Version18000Date20191014105105.php', + 'OC\\Core\\Migrations\\Version18000Date20191204114856' => __DIR__ . '/../../..' . '/core/Migrations/Version18000Date20191204114856.php', + 'OC\\Core\\Migrations\\Version19000Date20200211083441' => __DIR__ . '/../../..' . '/core/Migrations/Version19000Date20200211083441.php', + 'OC\\Core\\Notification\\RemoveLinkSharesNotifier' => __DIR__ . '/../../..' . '/core/Notification/RemoveLinkSharesNotifier.php', + 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', + 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', + 'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php', + 'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', + 'OC\\DB\\AdapterPgSql' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterPgSql.php', + 'OC\\DB\\AdapterSqlite' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterSqlite.php', + 'OC\\DB\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php', + 'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.php', + 'OC\\DB\\MDB2SchemaManager' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaManager.php', + 'OC\\DB\\MDB2SchemaReader' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaReader.php', + 'OC\\DB\\MDB2SchemaWriter' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaWriter.php', + 'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php', + 'OC\\DB\\MigrationService' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationService.php', + 'OC\\DB\\Migrator' => __DIR__ . '/../../..' . '/lib/private/DB/Migrator.php', + 'OC\\DB\\MissingColumnInformation' => __DIR__ . '/../../..' . '/lib/private/DB/MissingColumnInformation.php', + 'OC\\DB\\MissingIndexInformation' => __DIR__ . '/../../..' . '/lib/private/DB/MissingIndexInformation.php', + 'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php', + 'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php', + 'OC\\DB\\OCSqlitePlatform' => __DIR__ . '/../../..' . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\OracleConnection' => __DIR__ . '/../../..' . '/lib/private/DB/OracleConnection.php', + 'OC\\DB\\OracleMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/OracleMigrator.php', + 'OC\\DB\\PgSqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/PgSqlTools.php', + 'OC\\DB\\PostgreSqlMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/PostgreSqlMigrator.php', + 'OC\\DB\\QueryBuilder\\CompositeExpression' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/CompositeExpression.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\ExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\MySqlExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\OCIExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\PgSqlExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\ExpressionBuilder\\SqliteExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\FunctionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\OCIFunctionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\PgSqlFunctionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\FunctionBuilder\\SqliteFunctionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php', + 'OC\\DB\\QueryBuilder\\Literal' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/Literal.php', + 'OC\\DB\\QueryBuilder\\Parameter' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/Parameter.php', + 'OC\\DB\\QueryBuilder\\QueryBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/QueryBuilder.php', + 'OC\\DB\\QueryBuilder\\QueryFunction' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/QueryFunction.php', + 'OC\\DB\\QueryBuilder\\QuoteHelper' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/QuoteHelper.php', + 'OC\\DB\\ReconnectWrapper' => __DIR__ . '/../../..' . '/lib/private/DB/ReconnectWrapper.php', + 'OC\\DB\\SQLiteMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteMigrator.php', + 'OC\\DB\\SQLiteSessionInit' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteSessionInit.php', + 'OC\\DB\\SchemaWrapper' => __DIR__ . '/../../..' . '/lib/private/DB/SchemaWrapper.php', + 'OC\\DB\\SetTransactionIsolationLevel' => __DIR__ . '/../../..' . '/lib/private/DB/SetTransactionIsolationLevel.php', + 'OC\\Dashboard\\DashboardManager' => __DIR__ . '/../../..' . '/lib/private/Dashboard/DashboardManager.php', + 'OC\\Dashboard\\Manager' => __DIR__ . '/../../..' . '/lib/private/Dashboard/Manager.php', + 'OC\\DatabaseException' => __DIR__ . '/../../..' . '/lib/private/DatabaseException.php', + 'OC\\DatabaseSetupException' => __DIR__ . '/../../..' . '/lib/private/DatabaseSetupException.php', + 'OC\\DateTimeFormatter' => __DIR__ . '/../../..' . '/lib/private/DateTimeFormatter.php', + 'OC\\DateTimeZone' => __DIR__ . '/../../..' . '/lib/private/DateTimeZone.php', + 'OC\\Diagnostics\\Event' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/Event.php', + 'OC\\Diagnostics\\EventLogger' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/EventLogger.php', + 'OC\\Diagnostics\\Query' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/Query.php', + 'OC\\Diagnostics\\QueryLogger' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/QueryLogger.php', + 'OC\\DirectEditing\\Manager' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Manager.php', + 'OC\\DirectEditing\\Token' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Token.php', + 'OC\\Encryption\\DecryptAll' => __DIR__ . '/../../..' . '/lib/private/Encryption/DecryptAll.php', + 'OC\\Encryption\\EncryptionWrapper' => __DIR__ . '/../../..' . '/lib/private/Encryption/EncryptionWrapper.php', + 'OC\\Encryption\\Exceptions\\DecryptionFailedException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/DecryptionFailedException.php', + 'OC\\Encryption\\Exceptions\\EmptyEncryptionDataException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php', + 'OC\\Encryption\\Exceptions\\EncryptionFailedException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/EncryptionFailedException.php', + 'OC\\Encryption\\Exceptions\\EncryptionHeaderKeyExistsException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php', + 'OC\\Encryption\\Exceptions\\EncryptionHeaderToLargeException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php', + 'OC\\Encryption\\Exceptions\\ModuleAlreadyExistsException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php', + 'OC\\Encryption\\Exceptions\\ModuleDoesNotExistsException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php', + 'OC\\Encryption\\Exceptions\\UnknownCipherException' => __DIR__ . '/../../..' . '/lib/private/Encryption/Exceptions/UnknownCipherException.php', + 'OC\\Encryption\\File' => __DIR__ . '/../../..' . '/lib/private/Encryption/File.php', + 'OC\\Encryption\\HookManager' => __DIR__ . '/../../..' . '/lib/private/Encryption/HookManager.php', + 'OC\\Encryption\\Keys\\Storage' => __DIR__ . '/../../..' . '/lib/private/Encryption/Keys/Storage.php', + 'OC\\Encryption\\Manager' => __DIR__ . '/../../..' . '/lib/private/Encryption/Manager.php', + 'OC\\Encryption\\Update' => __DIR__ . '/../../..' . '/lib/private/Encryption/Update.php', + 'OC\\Encryption\\Util' => __DIR__ . '/../../..' . '/lib/private/Encryption/Util.php', + 'OC\\EventDispatcher\\EventDispatcher' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/EventDispatcher.php', + 'OC\\EventDispatcher\\GenericEventWrapper' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/GenericEventWrapper.php', + 'OC\\EventDispatcher\\ServiceEventListener' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/ServiceEventListener.php', + 'OC\\EventDispatcher\\SymfonyAdapter' => __DIR__ . '/../../..' . '/lib/private/EventDispatcher/SymfonyAdapter.php', + 'OC\\Federation\\CloudFederationFactory' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationFactory.php', + 'OC\\Federation\\CloudFederationNotification' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationNotification.php', + 'OC\\Federation\\CloudFederationProviderManager' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationProviderManager.php', + 'OC\\Federation\\CloudFederationShare' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationShare.php', + 'OC\\Federation\\CloudId' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudId.php', + 'OC\\Federation\\CloudIdManager' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudIdManager.php', + 'OC\\Files\\AppData\\AppData' => __DIR__ . '/../../..' . '/lib/private/Files/AppData/AppData.php', + 'OC\\Files\\AppData\\Factory' => __DIR__ . '/../../..' . '/lib/private/Files/AppData/Factory.php', + 'OC\\Files\\Cache\\AbstractCacheEvent' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/AbstractCacheEvent.php', + 'OC\\Files\\Cache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Cache.php', + 'OC\\Files\\Cache\\CacheEntry' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/CacheEntry.php', + 'OC\\Files\\Cache\\CacheQueryBuilder' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/CacheQueryBuilder.php', + 'OC\\Files\\Cache\\FailedCache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/FailedCache.php', + 'OC\\Files\\Cache\\HomeCache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/HomeCache.php', + 'OC\\Files\\Cache\\HomePropagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/HomePropagator.php', + 'OC\\Files\\Cache\\LocalRootScanner' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/LocalRootScanner.php', + 'OC\\Files\\Cache\\MoveFromCacheTrait' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/MoveFromCacheTrait.php', + 'OC\\Files\\Cache\\NullWatcher' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/NullWatcher.php', + 'OC\\Files\\Cache\\Propagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Propagator.php', + 'OC\\Files\\Cache\\QuerySearchHelper' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/QuerySearchHelper.php', + 'OC\\Files\\Cache\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Scanner.php', + 'OC\\Files\\Cache\\Storage' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Storage.php', + 'OC\\Files\\Cache\\StorageGlobal' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/StorageGlobal.php', + 'OC\\Files\\Cache\\Updater' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Updater.php', + 'OC\\Files\\Cache\\Watcher' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Watcher.php', + 'OC\\Files\\Cache\\Wrapper\\CacheJail' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CacheJail.php', + 'OC\\Files\\Cache\\Wrapper\\CachePermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php', + 'OC\\Files\\Cache\\Wrapper\\CacheWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CacheWrapper.php', + 'OC\\Files\\Cache\\Wrapper\\JailPropagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/JailPropagator.php', + 'OC\\Files\\Config\\CachedMountFileInfo' => __DIR__ . '/../../..' . '/lib/private/Files/Config/CachedMountFileInfo.php', + 'OC\\Files\\Config\\CachedMountInfo' => __DIR__ . '/../../..' . '/lib/private/Files/Config/CachedMountInfo.php', + 'OC\\Files\\Config\\LazyStorageMountInfo' => __DIR__ . '/../../..' . '/lib/private/Files/Config/LazyStorageMountInfo.php', + 'OC\\Files\\Config\\MountProviderCollection' => __DIR__ . '/../../..' . '/lib/private/Files/Config/MountProviderCollection.php', + 'OC\\Files\\Config\\UserMountCache' => __DIR__ . '/../../..' . '/lib/private/Files/Config/UserMountCache.php', + 'OC\\Files\\Config\\UserMountCacheListener' => __DIR__ . '/../../..' . '/lib/private/Files/Config/UserMountCacheListener.php', + 'OC\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/private/Files/FileInfo.php', + 'OC\\Files\\Filesystem' => __DIR__ . '/../../..' . '/lib/private/Files/Filesystem.php', + 'OC\\Files\\Mount\\CacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/CacheMountProvider.php', + 'OC\\Files\\Mount\\LocalHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/LocalHomeMountProvider.php', + 'OC\\Files\\Mount\\Manager' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/Manager.php', + 'OC\\Files\\Mount\\MountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MountPoint.php', + 'OC\\Files\\Mount\\MoveableMount' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MoveableMount.php', + 'OC\\Files\\Mount\\ObjectHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectHomeMountProvider.php', + 'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php', + 'OC\\Files\\Node\\File' => __DIR__ . '/../../..' . '/lib/private/Files/Node/File.php', + 'OC\\Files\\Node\\Folder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Folder.php', + 'OC\\Files\\Node\\HookConnector' => __DIR__ . '/../../..' . '/lib/private/Files/Node/HookConnector.php', + 'OC\\Files\\Node\\LazyFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyFolder.php', + 'OC\\Files\\Node\\LazyRoot' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\Node' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Node.php', + 'OC\\Files\\Node\\NonExistingFile' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFile.php', + 'OC\\Files\\Node\\NonExistingFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFolder.php', + 'OC\\Files\\Node\\Root' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Root.php', + 'OC\\Files\\Notify\\Change' => __DIR__ . '/../../..' . '/lib/private/Files/Notify/Change.php', + 'OC\\Files\\Notify\\RenameChange' => __DIR__ . '/../../..' . '/lib/private/Files/Notify/RenameChange.php', + 'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\Azure' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Azure.php', + 'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php', + 'OC\\Files\\ObjectStore\\NoopScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/NoopScanner.php', + 'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php', + 'OC\\Files\\ObjectStore\\S3' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3.php', + 'OC\\Files\\ObjectStore\\S3ConnectionTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php', + 'OC\\Files\\ObjectStore\\S3ObjectTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ObjectTrait.php', + 'OC\\Files\\ObjectStore\\S3Signature' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3Signature.php', + 'OC\\Files\\ObjectStore\\StorageObjectStore' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/StorageObjectStore.php', + 'OC\\Files\\ObjectStore\\Swift' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Swift.php', + 'OC\\Files\\ObjectStore\\SwiftFactory' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/SwiftFactory.php', + 'OC\\Files\\ObjectStore\\SwiftV2CachingAuthService' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/SwiftV2CachingAuthService.php', + 'OC\\Files\\Search\\SearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchBinaryOperator.php', + 'OC\\Files\\Search\\SearchComparison' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchComparison.php', + 'OC\\Files\\Search\\SearchOrder' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchOrder.php', + 'OC\\Files\\Search\\SearchQuery' => __DIR__ . '/../../..' . '/lib/private/Files/Search/SearchQuery.php', + 'OC\\Files\\SimpleFS\\NewSimpleFile' => __DIR__ . '/../../..' . '/lib/private/Files/SimpleFS/NewSimpleFile.php', + 'OC\\Files\\SimpleFS\\SimpleFile' => __DIR__ . '/../../..' . '/lib/private/Files/SimpleFS/SimpleFile.php', + 'OC\\Files\\SimpleFS\\SimpleFolder' => __DIR__ . '/../../..' . '/lib/private/Files/SimpleFS/SimpleFolder.php', + 'OC\\Files\\Storage\\Common' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Common.php', + 'OC\\Files\\Storage\\CommonTest' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/CommonTest.php', + 'OC\\Files\\Storage\\DAV' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/DAV.php', + 'OC\\Files\\Storage\\FailedStorage' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/FailedStorage.php', + 'OC\\Files\\Storage\\Flysystem' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Flysystem.php', + 'OC\\Files\\Storage\\Home' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Home.php', + 'OC\\Files\\Storage\\Local' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Local.php', + 'OC\\Files\\Storage\\LocalRootStorage' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/LocalRootStorage.php', + 'OC\\Files\\Storage\\LocalTempFileTrait' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/LocalTempFileTrait.php', + 'OC\\Files\\Storage\\PolyFill\\CopyDirectory' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/PolyFill/CopyDirectory.php', + 'OC\\Files\\Storage\\Storage' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Storage.php', + 'OC\\Files\\Storage\\StorageFactory' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/StorageFactory.php', + 'OC\\Files\\Storage\\Temporary' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Temporary.php', + 'OC\\Files\\Storage\\Wrapper\\Availability' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Availability.php', + 'OC\\Files\\Storage\\Wrapper\\Encoding' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Encoding.php', + 'OC\\Files\\Storage\\Wrapper\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Encryption.php', + 'OC\\Files\\Storage\\Wrapper\\Jail' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Jail.php', + 'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php', + 'OC\\Files\\Storage\\Wrapper\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Quota.php', + 'OC\\Files\\Storage\\Wrapper\\Wrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Wrapper.php', + 'OC\\Files\\Stream\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Encryption.php', + 'OC\\Files\\Stream\\HashWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/HashWrapper.php', + 'OC\\Files\\Stream\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Quota.php', + 'OC\\Files\\Stream\\SeekableHttpStream' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/SeekableHttpStream.php', + 'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php', + 'OC\\Files\\Type\\Loader' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Loader.php', + 'OC\\Files\\Type\\TemplateManager' => __DIR__ . '/../../..' . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Utils/Scanner.php', + 'OC\\Files\\View' => __DIR__ . '/../../..' . '/lib/private/Files/View.php', + 'OC\\ForbiddenException' => __DIR__ . '/../../..' . '/lib/private/ForbiddenException.php', + 'OC\\FullTextSearch\\FullTextSearchManager' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/FullTextSearchManager.php', + 'OC\\FullTextSearch\\Model\\DocumentAccess' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/Model/DocumentAccess.php', + 'OC\\FullTextSearch\\Model\\IndexDocument' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/Model/IndexDocument.php', + 'OC\\FullTextSearch\\Model\\SearchOption' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/Model/SearchOption.php', + 'OC\\FullTextSearch\\Model\\SearchRequestSimpleQuery' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php', + 'OC\\FullTextSearch\\Model\\SearchTemplate' => __DIR__ . '/../../..' . '/lib/private/FullTextSearch/Model/SearchTemplate.php', + 'OC\\GlobalScale\\Config' => __DIR__ . '/../../..' . '/lib/private/GlobalScale/Config.php', + 'OC\\Group\\Backend' => __DIR__ . '/../../..' . '/lib/private/Group/Backend.php', + 'OC\\Group\\Database' => __DIR__ . '/../../..' . '/lib/private/Group/Database.php', + 'OC\\Group\\Group' => __DIR__ . '/../../..' . '/lib/private/Group/Group.php', + 'OC\\Group\\Manager' => __DIR__ . '/../../..' . '/lib/private/Group/Manager.php', + 'OC\\Group\\MetaData' => __DIR__ . '/../../..' . '/lib/private/Group/MetaData.php', + 'OC\\HintException' => __DIR__ . '/../../..' . '/lib/private/HintException.php', + 'OC\\Hooks\\BasicEmitter' => __DIR__ . '/../../..' . '/lib/private/Hooks/BasicEmitter.php', + 'OC\\Hooks\\Emitter' => __DIR__ . '/../../..' . '/lib/private/Hooks/Emitter.php', + 'OC\\Hooks\\EmitterTrait' => __DIR__ . '/../../..' . '/lib/private/Hooks/EmitterTrait.php', + 'OC\\Hooks\\ForwardingEmitter' => __DIR__ . '/../../..' . '/lib/private/Hooks/ForwardingEmitter.php', + 'OC\\Hooks\\LegacyEmitter' => __DIR__ . '/../../..' . '/lib/private/Hooks/LegacyEmitter.php', + 'OC\\Hooks\\PublicEmitter' => __DIR__ . '/../../..' . '/lib/private/Hooks/PublicEmitter.php', + 'OC\\Http\\Client\\Client' => __DIR__ . '/../../..' . '/lib/private/Http/Client/Client.php', + 'OC\\Http\\Client\\ClientService' => __DIR__ . '/../../..' . '/lib/private/Http/Client/ClientService.php', + 'OC\\Http\\Client\\Response' => __DIR__ . '/../../..' . '/lib/private/Http/Client/Response.php', + 'OC\\Http\\CookieHelper' => __DIR__ . '/../../..' . '/lib/private/Http/CookieHelper.php', + 'OC\\InitialStateService' => __DIR__ . '/../../..' . '/lib/private/InitialStateService.php', + 'OC\\Installer' => __DIR__ . '/../../..' . '/lib/private/Installer.php', + 'OC\\IntegrityCheck\\Checker' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Checker.php', + 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php', + 'OC\\IntegrityCheck\\Helpers\\AppLocator' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Helpers/AppLocator.php', + 'OC\\IntegrityCheck\\Helpers\\EnvironmentHelper' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php', + 'OC\\IntegrityCheck\\Helpers\\FileAccessHelper' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php', + 'OC\\IntegrityCheck\\Iterator\\ExcludeFileByNameFilterIterator' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php', + 'OC\\IntegrityCheck\\Iterator\\ExcludeFoldersByPathFilterIterator' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php', + 'OC\\L10N\\Factory' => __DIR__ . '/../../..' . '/lib/private/L10N/Factory.php', + 'OC\\L10N\\L10N' => __DIR__ . '/../../..' . '/lib/private/L10N/L10N.php', + 'OC\\L10N\\L10NString' => __DIR__ . '/../../..' . '/lib/private/L10N/L10NString.php', + 'OC\\L10N\\LanguageIterator' => __DIR__ . '/../../..' . '/lib/private/L10N/LanguageIterator.php', + 'OC\\L10N\\LanguageNotFoundException' => __DIR__ . '/../../..' . '/lib/private/L10N/LanguageNotFoundException.php', + 'OC\\L10N\\LazyL10N' => __DIR__ . '/../../..' . '/lib/private/L10N/LazyL10N.php', + 'OC\\LargeFileHelper' => __DIR__ . '/../../..' . '/lib/private/LargeFileHelper.php', + 'OC\\Lock\\AbstractLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/AbstractLockingProvider.php', + 'OC\\Lock\\DBLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/DBLockingProvider.php', + 'OC\\Lock\\MemcacheLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/MemcacheLockingProvider.php', + 'OC\\Lock\\NoopLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => __DIR__ . '/../../..' . '/lib/private/Lockdown/LockdownManager.php', + 'OC\\Log' => __DIR__ . '/../../..' . '/lib/private/Log.php', + 'OC\\Log\\ErrorHandler' => __DIR__ . '/../../..' . '/lib/private/Log/ErrorHandler.php', + 'OC\\Log\\Errorlog' => __DIR__ . '/../../..' . '/lib/private/Log/Errorlog.php', + 'OC\\Log\\ExceptionSerializer' => __DIR__ . '/../../..' . '/lib/private/Log/ExceptionSerializer.php', + 'OC\\Log\\File' => __DIR__ . '/../../..' . '/lib/private/Log/File.php', + 'OC\\Log\\LogDetails' => __DIR__ . '/../../..' . '/lib/private/Log/LogDetails.php', + 'OC\\Log\\LogFactory' => __DIR__ . '/../../..' . '/lib/private/Log/LogFactory.php', + 'OC\\Log\\PsrLoggerAdapter' => __DIR__ . '/../../..' . '/lib/private/Log/PsrLoggerAdapter.php', + 'OC\\Log\\Rotate' => __DIR__ . '/../../..' . '/lib/private/Log/Rotate.php', + 'OC\\Log\\Syslog' => __DIR__ . '/../../..' . '/lib/private/Log/Syslog.php', + 'OC\\Log\\Systemdlog' => __DIR__ . '/../../..' . '/lib/private/Log/Systemdlog.php', + 'OC\\Mail\\Attachment' => __DIR__ . '/../../..' . '/lib/private/Mail/Attachment.php', + 'OC\\Mail\\EMailTemplate' => __DIR__ . '/../../..' . '/lib/private/Mail/EMailTemplate.php', + 'OC\\Mail\\Mailer' => __DIR__ . '/../../..' . '/lib/private/Mail/Mailer.php', + 'OC\\Mail\\Message' => __DIR__ . '/../../..' . '/lib/private/Mail/Message.php', + 'OC\\Memcache\\APCu' => __DIR__ . '/../../..' . '/lib/private/Memcache/APCu.php', + 'OC\\Memcache\\ArrayCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/ArrayCache.php', + 'OC\\Memcache\\CADTrait' => __DIR__ . '/../../..' . '/lib/private/Memcache/CADTrait.php', + 'OC\\Memcache\\CASTrait' => __DIR__ . '/../../..' . '/lib/private/Memcache/CASTrait.php', + 'OC\\Memcache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Memcache/Cache.php', + 'OC\\Memcache\\Factory' => __DIR__ . '/../../..' . '/lib/private/Memcache/Factory.php', + 'OC\\Memcache\\Memcached' => __DIR__ . '/../../..' . '/lib/private/Memcache/Memcached.php', + 'OC\\Memcache\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/NullCache.php', + 'OC\\Memcache\\Redis' => __DIR__ . '/../../..' . '/lib/private/Memcache/Redis.php', + 'OC\\MemoryInfo' => __DIR__ . '/../../..' . '/lib/private/MemoryInfo.php', + 'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php', + 'OC\\Migration\\ConsoleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/ConsoleOutput.php', + 'OC\\Migration\\SimpleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/SimpleOutput.php', + 'OC\\NaturalSort' => __DIR__ . '/../../..' . '/lib/private/NaturalSort.php', + 'OC\\NaturalSort_DefaultCollator' => __DIR__ . '/../../..' . '/lib/private/NaturalSort_DefaultCollator.php', + 'OC\\NavigationManager' => __DIR__ . '/../../..' . '/lib/private/NavigationManager.php', + 'OC\\NeedsUpdateException' => __DIR__ . '/../../..' . '/lib/private/NeedsUpdateException.php', + 'OC\\NotSquareException' => __DIR__ . '/../../..' . '/lib/private/NotSquareException.php', + 'OC\\Notification\\Action' => __DIR__ . '/../../..' . '/lib/private/Notification/Action.php', + 'OC\\Notification\\Manager' => __DIR__ . '/../../..' . '/lib/private/Notification/Manager.php', + 'OC\\Notification\\Notification' => __DIR__ . '/../../..' . '/lib/private/Notification/Notification.php', + 'OC\\OCS\\CoreCapabilities' => __DIR__ . '/../../..' . '/lib/private/OCS/CoreCapabilities.php', + 'OC\\OCS\\DiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCS/DiscoveryService.php', + 'OC\\OCS\\Exception' => __DIR__ . '/../../..' . '/lib/private/OCS/Exception.php', + 'OC\\OCS\\Provider' => __DIR__ . '/../../..' . '/lib/private/OCS/Provider.php', + 'OC\\OCS\\Result' => __DIR__ . '/../../..' . '/lib/private/OCS/Result.php', + 'OC\\PreviewManager' => __DIR__ . '/../../..' . '/lib/private/PreviewManager.php', + 'OC\\PreviewNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/PreviewNotAvailableException.php', + 'OC\\Preview\\BMP' => __DIR__ . '/../../..' . '/lib/private/Preview/BMP.php', + 'OC\\Preview\\BackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Preview/BackgroundCleanupJob.php', + 'OC\\Preview\\Bitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/Bitmap.php', + 'OC\\Preview\\Bundled' => __DIR__ . '/../../..' . '/lib/private/Preview/Bundled.php', + 'OC\\Preview\\Font' => __DIR__ . '/../../..' . '/lib/private/Preview/Font.php', + 'OC\\Preview\\GIF' => __DIR__ . '/../../..' . '/lib/private/Preview/GIF.php', + 'OC\\Preview\\Generator' => __DIR__ . '/../../..' . '/lib/private/Preview/Generator.php', + 'OC\\Preview\\GeneratorHelper' => __DIR__ . '/../../..' . '/lib/private/Preview/GeneratorHelper.php', + 'OC\\Preview\\HEIC' => __DIR__ . '/../../..' . '/lib/private/Preview/HEIC.php', + 'OC\\Preview\\Illustrator' => __DIR__ . '/../../..' . '/lib/private/Preview/Illustrator.php', + 'OC\\Preview\\Image' => __DIR__ . '/../../..' . '/lib/private/Preview/Image.php', + 'OC\\Preview\\JPEG' => __DIR__ . '/../../..' . '/lib/private/Preview/JPEG.php', + 'OC\\Preview\\Krita' => __DIR__ . '/../../..' . '/lib/private/Preview/Krita.php', + 'OC\\Preview\\MP3' => __DIR__ . '/../../..' . '/lib/private/Preview/MP3.php', + 'OC\\Preview\\MSOffice2003' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOffice2003.php', + 'OC\\Preview\\MSOffice2007' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOffice2007.php', + 'OC\\Preview\\MSOfficeDoc' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOfficeDoc.php', + 'OC\\Preview\\MarkDown' => __DIR__ . '/../../..' . '/lib/private/Preview/MarkDown.php', + 'OC\\Preview\\Movie' => __DIR__ . '/../../..' . '/lib/private/Preview/Movie.php', + 'OC\\Preview\\Office' => __DIR__ . '/../../..' . '/lib/private/Preview/Office.php', + 'OC\\Preview\\OpenDocument' => __DIR__ . '/../../..' . '/lib/private/Preview/OpenDocument.php', + 'OC\\Preview\\PDF' => __DIR__ . '/../../..' . '/lib/private/Preview/PDF.php', + 'OC\\Preview\\PNG' => __DIR__ . '/../../..' . '/lib/private/Preview/PNG.php', + 'OC\\Preview\\Photoshop' => __DIR__ . '/../../..' . '/lib/private/Preview/Photoshop.php', + 'OC\\Preview\\Postscript' => __DIR__ . '/../../..' . '/lib/private/Preview/Postscript.php', + 'OC\\Preview\\Provider' => __DIR__ . '/../../..' . '/lib/private/Preview/Provider.php', + 'OC\\Preview\\ProviderV1Adapter' => __DIR__ . '/../../..' . '/lib/private/Preview/ProviderV1Adapter.php', + 'OC\\Preview\\ProviderV2' => __DIR__ . '/../../..' . '/lib/private/Preview/ProviderV2.php', + 'OC\\Preview\\SVG' => __DIR__ . '/../../..' . '/lib/private/Preview/SVG.php', + 'OC\\Preview\\StarOffice' => __DIR__ . '/../../..' . '/lib/private/Preview/StarOffice.php', + 'OC\\Preview\\Storage\\Root' => __DIR__ . '/../../..' . '/lib/private/Preview/Storage/Root.php', + 'OC\\Preview\\TIFF' => __DIR__ . '/../../..' . '/lib/private/Preview/TIFF.php', + 'OC\\Preview\\TXT' => __DIR__ . '/../../..' . '/lib/private/Preview/TXT.php', + 'OC\\Preview\\Watcher' => __DIR__ . '/../../..' . '/lib/private/Preview/Watcher.php', + 'OC\\Preview\\WatcherConnector' => __DIR__ . '/../../..' . '/lib/private/Preview/WatcherConnector.php', + 'OC\\Preview\\XBitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/XBitmap.php', + 'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php', + 'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php', + 'OC\\Remote\\Api\\ApiCollection' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiCollection.php', + 'OC\\Remote\\Api\\ApiFactory' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiFactory.php', + 'OC\\Remote\\Api\\NotFoundException' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/NotFoundException.php', + 'OC\\Remote\\Api\\OCS' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/OCS.php', + 'OC\\Remote\\Credentials' => __DIR__ . '/../../..' . '/lib/private/Remote/Credentials.php', + 'OC\\Remote\\Instance' => __DIR__ . '/../../..' . '/lib/private/Remote/Instance.php', + 'OC\\Remote\\InstanceFactory' => __DIR__ . '/../../..' . '/lib/private/Remote/InstanceFactory.php', + 'OC\\Remote\\User' => __DIR__ . '/../../..' . '/lib/private/Remote/User.php', + 'OC\\Repair' => __DIR__ . '/../../..' . '/lib/private/Repair.php', + 'OC\\RepairException' => __DIR__ . '/../../..' . '/lib/private/RepairException.php', + 'OC\\Repair\\AddCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php', + 'OC\\Repair\\CleanTags' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanTags.php', + 'OC\\Repair\\ClearFrontendCaches' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearFrontendCaches.php', + 'OC\\Repair\\ClearGeneratedAvatarCache' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCache.php', + 'OC\\Repair\\Collation' => __DIR__ . '/../../..' . '/lib/private/Repair/Collation.php', + 'OC\\Repair\\MoveUpdaterStepFile' => __DIR__ . '/../../..' . '/lib/private/Repair/MoveUpdaterStepFile.php', + 'OC\\Repair\\NC11\\FixMountStorages' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/FixMountStorages.php', + 'OC\\Repair\\NC13\\AddLogRotateJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC13/AddLogRotateJob.php', + 'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php', + 'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php', + 'OC\\Repair\\NC16\\CleanupCardDAVPhotoCache' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php', + 'OC\\Repair\\NC16\\ClearCollectionsAccessCache' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/ClearCollectionsAccessCache.php', + 'OC\\Repair\\NC18\\ResetGeneratedAvatarFlag' => __DIR__ . '/../../..' . '/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php', + 'OC\\Repair\\NC20\\EncryptionLegacyCipher' => __DIR__ . '/../../..' . '/lib/private/Repair/NC20/EncryptionLegacyCipher.php', + 'OC\\Repair\\NC20\\EncryptionMigration' => __DIR__ . '/../../..' . '/lib/private/Repair/NC20/EncryptionMigration.php', + 'OC\\Repair\\NC20\\ShippedDashboardEnable' => __DIR__ . '/../../..' . '/lib/private/Repair/NC20/ShippedDashboardEnable.php', + 'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php', + 'OC\\Repair\\Owncloud\\DropAccountTermsTable' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php', + 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', + 'OC\\Repair\\RemoveLinkShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveLinkShares.php', + 'OC\\Repair\\RepairInvalidShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairInvalidShares.php', + 'OC\\Repair\\RepairMimeTypes' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairMimeTypes.php', + 'OC\\Repair\\SqliteAutoincrement' => __DIR__ . '/../../..' . '/lib/private/Repair/SqliteAutoincrement.php', + 'OC\\RichObjectStrings\\Validator' => __DIR__ . '/../../..' . '/lib/private/RichObjectStrings/Validator.php', + 'OC\\Route\\CachingRouter' => __DIR__ . '/../../..' . '/lib/private/Route/CachingRouter.php', + 'OC\\Route\\Route' => __DIR__ . '/../../..' . '/lib/private/Route/Route.php', + 'OC\\Route\\Router' => __DIR__ . '/../../..' . '/lib/private/Route/Router.php', + 'OC\\Search' => __DIR__ . '/../../..' . '/lib/private/Search.php', + 'OC\\Search\\Provider\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Provider/File.php', + 'OC\\Search\\Result\\Audio' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Audio.php', + 'OC\\Search\\Result\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Result/File.php', + 'OC\\Search\\Result\\Folder' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Folder.php', + 'OC\\Search\\Result\\Image' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Image.php', + 'OC\\Search\\SearchComposer' => __DIR__ . '/../../..' . '/lib/private/Search/SearchComposer.php', + 'OC\\Search\\SearchQuery' => __DIR__ . '/../../..' . '/lib/private/Search/SearchQuery.php', + 'OC\\Security\\Bruteforce\\Capabilities' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Capabilities.php', + 'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php', + 'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php', + 'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php', + 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php', + 'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php', + 'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php', + 'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php', + 'OC\\Security\\CSRF\\TokenStorage\\SessionStorage' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/TokenStorage/SessionStorage.php', + 'OC\\Security\\Certificate' => __DIR__ . '/../../..' . '/lib/private/Security/Certificate.php', + 'OC\\Security\\CertificateManager' => __DIR__ . '/../../..' . '/lib/private/Security/CertificateManager.php', + 'OC\\Security\\CredentialsManager' => __DIR__ . '/../../..' . '/lib/private/Security/CredentialsManager.php', + 'OC\\Security\\Crypto' => __DIR__ . '/../../..' . '/lib/private/Security/Crypto.php', + 'OC\\Security\\FeaturePolicy\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', + 'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', + 'OC\\Security\\Hasher' => __DIR__ . '/../../..' . '/lib/private/Security/Hasher.php', + 'OC\\Security\\IdentityProof\\Key' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Key.php', + 'OC\\Security\\IdentityProof\\Manager' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Manager.php', + 'OC\\Security\\IdentityProof\\Signer' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Signer.php', + 'OC\\Security\\Normalizer\\IpAddress' => __DIR__ . '/../../..' . '/lib/private/Security/Normalizer/IpAddress.php', + 'OC\\Security\\RateLimiting\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/IBackend.php', + 'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php', + 'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php', + 'OC\\Security\\RateLimiting\\Limiter' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Limiter.php', + 'OC\\Security\\SecureRandom' => __DIR__ . '/../../..' . '/lib/private/Security/SecureRandom.php', + 'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php', + 'OC\\Server' => __DIR__ . '/../../..' . '/lib/private/Server.php', + 'OC\\ServerContainer' => __DIR__ . '/../../..' . '/lib/private/ServerContainer.php', + 'OC\\ServerNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/ServerNotAvailableException.php', + 'OC\\ServiceUnavailableException' => __DIR__ . '/../../..' . '/lib/private/ServiceUnavailableException.php', + 'OC\\Session\\CryptoSessionData' => __DIR__ . '/../../..' . '/lib/private/Session/CryptoSessionData.php', + 'OC\\Session\\CryptoWrapper' => __DIR__ . '/../../..' . '/lib/private/Session/CryptoWrapper.php', + 'OC\\Session\\Internal' => __DIR__ . '/../../..' . '/lib/private/Session/Internal.php', + 'OC\\Session\\Memory' => __DIR__ . '/../../..' . '/lib/private/Session/Memory.php', + 'OC\\Session\\Session' => __DIR__ . '/../../..' . '/lib/private/Session/Session.php', + 'OC\\Settings\\Manager' => __DIR__ . '/../../..' . '/lib/private/Settings/Manager.php', + 'OC\\Settings\\Section' => __DIR__ . '/../../..' . '/lib/private/Settings/Section.php', + 'OC\\Setup' => __DIR__ . '/../../..' . '/lib/private/Setup.php', + 'OC\\Setup\\AbstractDatabase' => __DIR__ . '/../../..' . '/lib/private/Setup/AbstractDatabase.php', + 'OC\\Setup\\MySQL' => __DIR__ . '/../../..' . '/lib/private/Setup/MySQL.php', + 'OC\\Setup\\OCI' => __DIR__ . '/../../..' . '/lib/private/Setup/OCI.php', + 'OC\\Setup\\PostgreSQL' => __DIR__ . '/../../..' . '/lib/private/Setup/PostgreSQL.php', + 'OC\\Setup\\Sqlite' => __DIR__ . '/../../..' . '/lib/private/Setup/Sqlite.php', + 'OC\\Share20\\DefaultShareProvider' => __DIR__ . '/../../..' . '/lib/private/Share20/DefaultShareProvider.php', + 'OC\\Share20\\Exception\\BackendError' => __DIR__ . '/../../..' . '/lib/private/Share20/Exception/BackendError.php', + 'OC\\Share20\\Exception\\InvalidShare' => __DIR__ . '/../../..' . '/lib/private/Share20/Exception/InvalidShare.php', + 'OC\\Share20\\Exception\\ProviderException' => __DIR__ . '/../../..' . '/lib/private/Share20/Exception/ProviderException.php', + 'OC\\Share20\\Hooks' => __DIR__ . '/../../..' . '/lib/private/Share20/Hooks.php', + 'OC\\Share20\\LegacyHooks' => __DIR__ . '/../../..' . '/lib/private/Share20/LegacyHooks.php', + 'OC\\Share20\\Manager' => __DIR__ . '/../../..' . '/lib/private/Share20/Manager.php', + 'OC\\Share20\\ProviderFactory' => __DIR__ . '/../../..' . '/lib/private/Share20/ProviderFactory.php', + 'OC\\Share20\\Share' => __DIR__ . '/../../..' . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareHelper' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareHelper.php', + 'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php', + 'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php', + 'OC\\Share\\Helper' => __DIR__ . '/../../..' . '/lib/private/Share/Helper.php', + 'OC\\Share\\SearchResultSorter' => __DIR__ . '/../../..' . '/lib/private/Share/SearchResultSorter.php', + 'OC\\Share\\Share' => __DIR__ . '/../../..' . '/lib/private/Share/Share.php', + 'OC\\Streamer' => __DIR__ . '/../../..' . '/lib/private/Streamer.php', + 'OC\\SubAdmin' => __DIR__ . '/../../..' . '/lib/private/SubAdmin.php', + 'OC\\Support\\CrashReport\\Registry' => __DIR__ . '/../../..' . '/lib/private/Support/CrashReport/Registry.php', + 'OC\\Support\\Subscription\\Registry' => __DIR__ . '/../../..' . '/lib/private/Support/Subscription/Registry.php', + 'OC\\SystemConfig' => __DIR__ . '/../../..' . '/lib/private/SystemConfig.php', + 'OC\\SystemTag\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/SystemTag/ManagerFactory.php', + 'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php', + 'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php', + 'OC\\SystemTag\\SystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\TagManager' => __DIR__ . '/../../..' . '/lib/private/TagManager.php', + 'OC\\Tagging\\Tag' => __DIR__ . '/../../..' . '/lib/private/Tagging/Tag.php', + 'OC\\Tagging\\TagMapper' => __DIR__ . '/../../..' . '/lib/private/Tagging/TagMapper.php', + 'OC\\Tags' => __DIR__ . '/../../..' . '/lib/private/Tags.php', + 'OC\\TempManager' => __DIR__ . '/../../..' . '/lib/private/TempManager.php', + 'OC\\TemplateLayout' => __DIR__ . '/../../..' . '/lib/private/TemplateLayout.php', + 'OC\\Template\\Base' => __DIR__ . '/../../..' . '/lib/private/Template/Base.php', + 'OC\\Template\\CSSResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/CSSResourceLocator.php', + 'OC\\Template\\IconsCacher' => __DIR__ . '/../../..' . '/lib/private/Template/IconsCacher.php', + 'OC\\Template\\JSCombiner' => __DIR__ . '/../../..' . '/lib/private/Template/JSCombiner.php', + 'OC\\Template\\JSConfigHelper' => __DIR__ . '/../../..' . '/lib/private/Template/JSConfigHelper.php', + 'OC\\Template\\JSResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/JSResourceLocator.php', + 'OC\\Template\\ResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/ResourceLocator.php', + 'OC\\Template\\ResourceNotFoundException' => __DIR__ . '/../../..' . '/lib/private/Template/ResourceNotFoundException.php', + 'OC\\Template\\SCSSCacher' => __DIR__ . '/../../..' . '/lib/private/Template/SCSSCacher.php', + 'OC\\Template\\TemplateFileLocator' => __DIR__ . '/../../..' . '/lib/private/Template/TemplateFileLocator.php', + 'OC\\URLGenerator' => __DIR__ . '/../../..' . '/lib/private/URLGenerator.php', + 'OC\\Updater' => __DIR__ . '/../../..' . '/lib/private/Updater.php', + 'OC\\Updater\\ChangesCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesCheck.php', + 'OC\\Updater\\ChangesMapper' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesMapper.php', + 'OC\\Updater\\ChangesResult' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesResult.php', + 'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php', + 'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php', + 'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php', + 'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php', + 'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php', + 'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php', + 'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', + 'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php', + 'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php', + 'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php', + 'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php', + 'OC_DB' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_DB.php', + 'OC_DB_StatementWrapper' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_DB_StatementWrapper.php', + 'OC_Defaults' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Defaults.php', + 'OC_EventSource' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_EventSource.php', + 'OC_FileChunking' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_FileChunking.php', + 'OC_Files' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Files.php', + 'OC_Helper' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Helper.php', + 'OC_Hook' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Hook.php', + 'OC_Image' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Image.php', + 'OC_JSON' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_JSON.php', + 'OC_Response' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Response.php', + 'OC_Template' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Template.php', + 'OC_User' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_User.php', + 'OC_Util' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Util.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$prefixDirsPsr4; + $loader->fallbackDirsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$fallbackDirsPsr4; + $loader->classMap = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/docker/overlays/nextcloud/html/lib/composer/composer/installed.json b/docker/overlays/nextcloud/html/lib/composer/composer/installed.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/composer/composer/installed.json @@ -0,0 +1 @@ +[] diff --git a/docker/overlays/nextcloud/html/lib/l10n/af.js b/docker/overlays/nextcloud/html/lib/l10n/af.js new file mode 100644 index 0000000..f0bb7a7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/af.js @@ -0,0 +1,27 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "Onbekende lêertipe", + "Invalid image" : "Ongeldige beeld", + "seconds ago" : "sekondes gelede", + "__language_name__" : "Afrikaans", + "Help" : "Hulp", + "Apps" : "Toeps", + "Settings" : "Instellings", + "Log out" : "Meld af", + "Users" : "Gebruikers", + "Unknown user" : "Onbekende gebruiker", + "Open »%s«" : "Open »%s«", + "Sunday" : "Sondag", + "Monday" : "Maandag", + "Tuesday" : "Dinsdag", + "Wednesday" : "Woensdag", + "Thursday" : "Donderdag", + "Friday" : "Vrydag", + "Saturday" : "Saterdag", + "a safe home for all your data" : "’n veilige tuiste vir al u data", + "Storage is temporarily not available" : "Berging is tydelik nie beskikbaar nie", + "Security" : "Sekuriteit", + "Personal info" : "Persoonlike inligting" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/af.json b/docker/overlays/nextcloud/html/lib/l10n/af.json new file mode 100644 index 0000000..efc6a26 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/af.json @@ -0,0 +1,25 @@ +{ "translations": { + "Unknown filetype" : "Onbekende lêertipe", + "Invalid image" : "Ongeldige beeld", + "seconds ago" : "sekondes gelede", + "__language_name__" : "Afrikaans", + "Help" : "Hulp", + "Apps" : "Toeps", + "Settings" : "Instellings", + "Log out" : "Meld af", + "Users" : "Gebruikers", + "Unknown user" : "Onbekende gebruiker", + "Open »%s«" : "Open »%s«", + "Sunday" : "Sondag", + "Monday" : "Maandag", + "Tuesday" : "Dinsdag", + "Wednesday" : "Woensdag", + "Thursday" : "Donderdag", + "Friday" : "Vrydag", + "Saturday" : "Saterdag", + "a safe home for all your data" : "’n veilige tuiste vir al u data", + "Storage is temporarily not available" : "Berging is tydelik nie beskikbaar nie", + "Security" : "Sekuriteit", + "Personal info" : "Persoonlike inligting" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ar.js b/docker/overlays/nextcloud/html/lib/l10n/ar.js new file mode 100644 index 0000000..b5fd998 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ar.js @@ -0,0 +1,130 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "الكتابة في مجلد \"config\" غير ممكنة!", + "This can usually be fixed by giving the webserver write access to the config directory" : "يمكن حل هذا عادة بإعطاء خادم الوب صلاحية الكتابة في مجلد config", + "See %s" : "أنظر %s", + "Sample configuration detected" : "تم اكتشاف إعدادات عيّنة", + "%1$s and %2$s" : "%1$s و %2$s", + "%1$s, %2$s and %3$s" : "%1$s، %2$s و %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s، %2$s، %3$s و %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s، %2$s، %3$s، %4$s و %5$s", + "Education Edition" : "الإصدار التعليمي", + "Enterprise bundle" : "حزمة المؤسسة", + "PHP %s or higher is required." : "إصدار PHP %s أو أحدث منه مطلوب.", + "PHP with a version lower than %s is required." : "PHP الإصدار %s أو أقل مطلوب.", + "%sbit or higher PHP required." : "مكتبات PHP ذات %s بت أو أعلى مطلوبة.", + "The command line tool %s could not be found" : "لم يتم العثور على أداة سطر الأوامر %s", + "The library %s is not available." : "مكتبة %s غير متوفرة.", + "Authentication" : "المصادقة", + "Unknown filetype" : "نوع الملف غير معروف", + "Invalid image" : "الصورة غير صالحة", + "Avatar image is not square" : "الصورة الرمزية ليست على شكل مربّع", + "today" : "اليوم", + "tomorrow" : "غدًا", + "yesterday" : "يوم أمس", + "_%n day ago_::_%n days ago_" : ["قبل ساعات","قبل يوم","قبل يومين","قبل %n يوماً","قبل %n يوماً","قبل %n يوماً"], + "next month" : "الشهر القادم", + "last month" : "الشهر الماضي", + "_%n month ago_::_%n months ago_" : ["قبل عدة أيام","قبل شهر","قبل شهرين","قبل %n شهراً","قبل %n شهراً","قبل %n شهراً"], + "next year" : "العام القادم", + "last year" : "السنةالماضية", + "in a few seconds" : "خلال بضع ثواني", + "seconds ago" : "منذ ثواني", + "File name is a reserved word" : "اسم الملف كلمة محجوزة", + "File name contains at least one invalid character" : "اسم الملف به ، على الأقل ، حرف غير صالح", + "File name is too long" : "اسم الملف طويل جداً", + "Empty filename is not allowed" : "لا يسمح بأسماء فارغة للملفات", + "__language_name__" : "اللغة العربية", + "Help" : "المساعدة", + "Apps" : "التطبيقات", + "Settings" : "الإعدادات", + "Log out" : "الخروج", + "Users" : "المستخدمين", + "Unknown user" : "المستخدم غير معروف", + "Additional settings" : "الإعدادات المتقدمة", + "%s enter the database username and name." : "%s أدخِل اسم قاعدة البيانات واسم مستخدمها.", + "%s enter the database username." : "%s ادخل اسم المستخدم الخاص بقاعدة البيانات.", + "%s enter the database name." : "%s ادخل اسم فاعدة البيانات", + "%s you may not use dots in the database name" : "%s لا يسمح لك باستخدام نقطه (.) في اسم قاعدة البيانات", + "Oracle connection could not be established" : "لم تنجح محاولة اتصال Oracle", + "Oracle username and/or password not valid" : "اسم المستخدم و/أو كلمة المرور لنظام Oracle غير صحيح", + "PostgreSQL username and/or password not valid" : "اسم المستخدم / أو كلمة المرور الخاصة بـPostgreSQL غير صحيحة", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "نظام ماك الإصدار X غير مدعوم و %s لن يعمل بشكل صحيح على هذه المنصة. استخدمه على مسؤوليتك!", + "For the best results, please consider using a GNU/Linux server instead." : "فضلاً ضع في الاعتبار استخدام نظام GNU/Linux بدل الأنظمة الأخرى للحصول على أفضل النتائج.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "فضلاً إحذف إعداد open_basedir من ملف php.ini لديك أو حوّل إلى PHP إصدار 64 بت.", + "Set an admin username." : "اعداد اسم مستخدم للمدير", + "Set an admin password." : "اعداد كلمة مرور للمدير", + "Can't create or write into the data directory %s" : "لا يمكن الإنشاء أو الكتابة في مجلد البيانات %s", + "Invalid Federated Cloud ID" : "معرّف سحابة الاتحاد غير صالح", + "You are not allowed to share %s" : "أنت غير مسموح لك أن تشارك %s", + "Click the button below to open it." : "أنقر على الزر أدناه لفتحه.", + "Could not find category \"%s\"" : "تعذر العثور على المجلد \"%s\"", + "Sunday" : "الأحد", + "Monday" : "الإثنين", + "Tuesday" : "الثلاثاء", + "Wednesday" : "الأربعاء", + "Thursday" : "الخميس", + "Friday" : "الجمعة", + "Saturday" : "السبت", + "Sun." : "أح.", + "Mon." : "إث.", + "Tue." : "ثلا.", + "Wed." : "أر.", + "Thu." : "خم.", + "Fri." : "جم.", + "Sat." : "سب.", + "Su" : "أح", + "Mo" : "إث", + "Tu" : "ثلا", + "We" : "أر", + "Th" : "خم", + "Fr" : "جم", + "Sa" : "سب", + "January" : "جانفي", + "February" : "فيفري", + "March" : "مارس", + "April" : "أفريل", + "May" : "ماي", + "June" : "جوان", + "July" : "جويلية", + "August" : "أوت", + "September" : "سبتمبر", + "October" : "أكتوبر", + "November" : "نوفمبر", + "December" : "ديسمبر", + "Jan." : "جان.", + "Feb." : "فيف.", + "Mar." : "مار.", + "Apr." : "أفر.", + "May." : "ماي", + "Jun." : "جوا.", + "Jul." : "جوي.", + "Aug." : "أوت", + "Sep." : "سبت.", + "Oct." : "أكت.", + "Nov." : "نوف.", + "Dec." : "ديس.", + "A valid username must be provided" : "يجب ادخال اسم مستخدم صحيح", + "Username contains whitespace at the beginning or at the end" : "إنّ إسم المستخدم يحتوي على مسافة بيضاء سواءا في البداية أو النهاية", + "A valid password must be provided" : "يجب ادخال كلمة مرور صحيحة", + "Could not create user" : "لا يمكن إنشاء المستخدم", + "User disabled" : "المستخدم معطّل", + "Login canceled by app" : "تم إلغاء الدخول مِن طرف التطبيق", + "a safe home for all your data" : "المكان الآمن لجميع بياناتك", + "File is currently busy, please try again later" : "إنّ الملف مشغول الآمن، يرجى إعادة المحاولة لاحقًا", + "Can't read file" : "لا يمكن قراءة الملف", + "Application is not enabled" : "التطبيق غير مفعّل", + "Authentication error" : "لم يتم التأكد من الشخصية بنجاح", + "Token expired. Please reload page." : "انتهت صلاحية الكلمة , يرجى اعادة تحميل الصفحة", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s", + "Storage is temporarily not available" : "وحدة التخزين غير متوفرة", + "Following databases are supported: %s" : "قواعد البيانات التالية مدعومة: %s", + "Overview" : "نظرة شاملة", + "Basic settings" : "الإعدادات الأساسية", + "Sharing" : "المشاركة", + "Security" : "الأمان", + "Personal info" : "المعلومات الشخصية", + "Mobile & desktop" : "الجوال وسطح المكتب" +}, +"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ar.json b/docker/overlays/nextcloud/html/lib/l10n/ar.json new file mode 100644 index 0000000..3a8358c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ar.json @@ -0,0 +1,128 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "الكتابة في مجلد \"config\" غير ممكنة!", + "This can usually be fixed by giving the webserver write access to the config directory" : "يمكن حل هذا عادة بإعطاء خادم الوب صلاحية الكتابة في مجلد config", + "See %s" : "أنظر %s", + "Sample configuration detected" : "تم اكتشاف إعدادات عيّنة", + "%1$s and %2$s" : "%1$s و %2$s", + "%1$s, %2$s and %3$s" : "%1$s، %2$s و %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s، %2$s، %3$s و %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s، %2$s، %3$s، %4$s و %5$s", + "Education Edition" : "الإصدار التعليمي", + "Enterprise bundle" : "حزمة المؤسسة", + "PHP %s or higher is required." : "إصدار PHP %s أو أحدث منه مطلوب.", + "PHP with a version lower than %s is required." : "PHP الإصدار %s أو أقل مطلوب.", + "%sbit or higher PHP required." : "مكتبات PHP ذات %s بت أو أعلى مطلوبة.", + "The command line tool %s could not be found" : "لم يتم العثور على أداة سطر الأوامر %s", + "The library %s is not available." : "مكتبة %s غير متوفرة.", + "Authentication" : "المصادقة", + "Unknown filetype" : "نوع الملف غير معروف", + "Invalid image" : "الصورة غير صالحة", + "Avatar image is not square" : "الصورة الرمزية ليست على شكل مربّع", + "today" : "اليوم", + "tomorrow" : "غدًا", + "yesterday" : "يوم أمس", + "_%n day ago_::_%n days ago_" : ["قبل ساعات","قبل يوم","قبل يومين","قبل %n يوماً","قبل %n يوماً","قبل %n يوماً"], + "next month" : "الشهر القادم", + "last month" : "الشهر الماضي", + "_%n month ago_::_%n months ago_" : ["قبل عدة أيام","قبل شهر","قبل شهرين","قبل %n شهراً","قبل %n شهراً","قبل %n شهراً"], + "next year" : "العام القادم", + "last year" : "السنةالماضية", + "in a few seconds" : "خلال بضع ثواني", + "seconds ago" : "منذ ثواني", + "File name is a reserved word" : "اسم الملف كلمة محجوزة", + "File name contains at least one invalid character" : "اسم الملف به ، على الأقل ، حرف غير صالح", + "File name is too long" : "اسم الملف طويل جداً", + "Empty filename is not allowed" : "لا يسمح بأسماء فارغة للملفات", + "__language_name__" : "اللغة العربية", + "Help" : "المساعدة", + "Apps" : "التطبيقات", + "Settings" : "الإعدادات", + "Log out" : "الخروج", + "Users" : "المستخدمين", + "Unknown user" : "المستخدم غير معروف", + "Additional settings" : "الإعدادات المتقدمة", + "%s enter the database username and name." : "%s أدخِل اسم قاعدة البيانات واسم مستخدمها.", + "%s enter the database username." : "%s ادخل اسم المستخدم الخاص بقاعدة البيانات.", + "%s enter the database name." : "%s ادخل اسم فاعدة البيانات", + "%s you may not use dots in the database name" : "%s لا يسمح لك باستخدام نقطه (.) في اسم قاعدة البيانات", + "Oracle connection could not be established" : "لم تنجح محاولة اتصال Oracle", + "Oracle username and/or password not valid" : "اسم المستخدم و/أو كلمة المرور لنظام Oracle غير صحيح", + "PostgreSQL username and/or password not valid" : "اسم المستخدم / أو كلمة المرور الخاصة بـPostgreSQL غير صحيحة", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "نظام ماك الإصدار X غير مدعوم و %s لن يعمل بشكل صحيح على هذه المنصة. استخدمه على مسؤوليتك!", + "For the best results, please consider using a GNU/Linux server instead." : "فضلاً ضع في الاعتبار استخدام نظام GNU/Linux بدل الأنظمة الأخرى للحصول على أفضل النتائج.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "فضلاً إحذف إعداد open_basedir من ملف php.ini لديك أو حوّل إلى PHP إصدار 64 بت.", + "Set an admin username." : "اعداد اسم مستخدم للمدير", + "Set an admin password." : "اعداد كلمة مرور للمدير", + "Can't create or write into the data directory %s" : "لا يمكن الإنشاء أو الكتابة في مجلد البيانات %s", + "Invalid Federated Cloud ID" : "معرّف سحابة الاتحاد غير صالح", + "You are not allowed to share %s" : "أنت غير مسموح لك أن تشارك %s", + "Click the button below to open it." : "أنقر على الزر أدناه لفتحه.", + "Could not find category \"%s\"" : "تعذر العثور على المجلد \"%s\"", + "Sunday" : "الأحد", + "Monday" : "الإثنين", + "Tuesday" : "الثلاثاء", + "Wednesday" : "الأربعاء", + "Thursday" : "الخميس", + "Friday" : "الجمعة", + "Saturday" : "السبت", + "Sun." : "أح.", + "Mon." : "إث.", + "Tue." : "ثلا.", + "Wed." : "أر.", + "Thu." : "خم.", + "Fri." : "جم.", + "Sat." : "سب.", + "Su" : "أح", + "Mo" : "إث", + "Tu" : "ثلا", + "We" : "أر", + "Th" : "خم", + "Fr" : "جم", + "Sa" : "سب", + "January" : "جانفي", + "February" : "فيفري", + "March" : "مارس", + "April" : "أفريل", + "May" : "ماي", + "June" : "جوان", + "July" : "جويلية", + "August" : "أوت", + "September" : "سبتمبر", + "October" : "أكتوبر", + "November" : "نوفمبر", + "December" : "ديسمبر", + "Jan." : "جان.", + "Feb." : "فيف.", + "Mar." : "مار.", + "Apr." : "أفر.", + "May." : "ماي", + "Jun." : "جوا.", + "Jul." : "جوي.", + "Aug." : "أوت", + "Sep." : "سبت.", + "Oct." : "أكت.", + "Nov." : "نوف.", + "Dec." : "ديس.", + "A valid username must be provided" : "يجب ادخال اسم مستخدم صحيح", + "Username contains whitespace at the beginning or at the end" : "إنّ إسم المستخدم يحتوي على مسافة بيضاء سواءا في البداية أو النهاية", + "A valid password must be provided" : "يجب ادخال كلمة مرور صحيحة", + "Could not create user" : "لا يمكن إنشاء المستخدم", + "User disabled" : "المستخدم معطّل", + "Login canceled by app" : "تم إلغاء الدخول مِن طرف التطبيق", + "a safe home for all your data" : "المكان الآمن لجميع بياناتك", + "File is currently busy, please try again later" : "إنّ الملف مشغول الآمن، يرجى إعادة المحاولة لاحقًا", + "Can't read file" : "لا يمكن قراءة الملف", + "Application is not enabled" : "التطبيق غير مفعّل", + "Authentication error" : "لم يتم التأكد من الشخصية بنجاح", + "Token expired. Please reload page." : "انتهت صلاحية الكلمة , يرجى اعادة تحميل الصفحة", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s", + "Storage is temporarily not available" : "وحدة التخزين غير متوفرة", + "Following databases are supported: %s" : "قواعد البيانات التالية مدعومة: %s", + "Overview" : "نظرة شاملة", + "Basic settings" : "الإعدادات الأساسية", + "Sharing" : "المشاركة", + "Security" : "الأمان", + "Personal info" : "المعلومات الشخصية", + "Mobile & desktop" : "الجوال وسطح المكتب" +},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ast.js b/docker/overlays/nextcloud/html/lib/l10n/ast.js new file mode 100644 index 0000000..e173825 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ast.js @@ -0,0 +1,170 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡Nun pue escribise nel direutoriu «config»!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Davezu esto pue iguase dándo-y al sirvidor web accesu d'escritura al direutoriu de configuración", + "See %s" : "Mira %s", + "Sample configuration detected" : "Configuración d'amuesa detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectose que la configuración d'amuesa copiose. Esto pue encaboxar la instalación y dexala ensín soporte. Llee la documentación enantes de facer cambéos en config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "Education Edition" : "Edición educativa", + "Enterprise bundle" : "Llote empresarial", + "Social sharing bundle" : "Llote de compartición social", + "PHP %s or higher is required." : "Necesítase PHP %s o superior", + "PHP with a version lower than %s is required." : "Necesítase una versión PHP anterior a %s", + "%sbit or higher PHP required." : "Necesítase PHP %sbit o superior", + "The command line tool %s could not be found" : "La ferramienta línea de comandu %s nun pudo alcontrase", + "The library %s is not available." : "La librería %s nun ta disponible", + "Authentication" : "Autenticación", + "Unknown filetype" : "Triba de ficheru desconocida", + "Invalid image" : "Imaxe inválida", + "Avatar image is not square" : "La imaxe del avatar nun ye cuadrada", + "today" : "güei", + "tomorrow" : "mañana", + "yesterday" : "ayeri", + "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n díes"], + "last month" : "mes caberu", + "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"], + "last year" : "añu caberu", + "_%n year ago_::_%n years ago_" : ["hai %n añu","hai %n años"], + "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n hores"], + "_%n minute ago_::_%n minutes ago_" : ["hai %n minutu","hai %n minutos"], + "seconds ago" : "hai segundos", + "File name is a reserved word" : "El nome de ficheru ye una pallabra reservada", + "File name contains at least one invalid character" : "El nome del ficheru contién polo menos un carácter non válidu", + "File name is too long" : "El nome de ficheru ye demasiáu llargu", + "Empty filename is not allowed" : "Nun s'almite un nome de ficheru baleru", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicación \"%s\" nun puede instalase porque nun se llee'l ficheru appinfo.", + "__language_name__" : "Asturianu", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Settings", + "Log out" : "Zarrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Usuariu desconocíu", + "Additional settings" : "Axustes adicionales", + "%s enter the database username and name." : "%s introducir el nome d'usuariu y el nome de la base de datos .", + "%s enter the database username." : "%s introducir l'usuariu de la base de datos.", + "%s enter the database name." : "%s introducir nome de la base de datos.", + "%s you may not use dots in the database name" : "%s nun pues usar puntos nel nome de la base de datos", + "You need to enter details of an existing account." : "Precises introducir los detalles d'una cuenta esistente.", + "Oracle connection could not be established" : "Nun pudo afitase la conexón d'Oracle", + "Oracle username and/or password not valid" : "Nome d'usuariu o contraseña d'Oracle non válidos", + "PostgreSQL username and/or password not valid" : "Nome d'usuariu o contraseña PostgreSQL non válidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nun ta sofitáu y %s nun furrulará afayadizamente nesta plataforma. ¡Úsalu baxo'l to riesgu!", + "For the best results, please consider using a GNU/Linux server instead." : "Pa los meyores resultaos, por favor considera l'usu d'un sirvidor GNU/Linux nel so llugar.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Paez ser que la instancia %s ta executándose nun entornu de PHP 32 bits y el open_basedir configuróse en php.ini. Esto va dar llugar a problemes colos ficheros de más de 4 GB y nun ye nada recomendable.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, desanicia la configuración open_basedir dientro la so php.ini o camude a PHP 64 bits.", + "Set an admin username." : "Afitar nome d'usuariu p'almin", + "Set an admin password." : "Afitar contraseña p'almin", + "Can't create or write into the data directory %s" : "Nun pue crease o escribir dientro los datos del direutoriu %s", + "Invalid Federated Cloud ID" : "ID non válida de ñube federada", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartíu %s tien d'implementar la interfaz OCP\\Share_Backend", + "Sharing backend %s not found" : "Nun s'alcontró'l botón de compartición %s", + "Sharing backend for %s not found" : "Nun s'alcontró'l botón de partición pa %s", + "Open »%s«" : "Abrir «%s»", + "You are not allowed to share %s" : "Nun tienes permisu pa compartir %s", + "Can’t increase permissions of %s" : "Nun pue aumentase los permisos de %s", + "Files can’t be shared with delete permissions" : "Los ficheros nun puen compartise colos permisos de desaniciu", + "Files can’t be shared with create permissions" : "Los ficheros nun puen compartise colos permisos de creación", + "Expiration date is in the past" : "La data de caducidá ta nel pasáu.", + "Can’t set expiration date more than %s days in the future" : "Nun pue afitase la data de caducidá más de %s díes nel futuru", + "Click the button below to open it." : "Primi'l botón d'embaxo p'abrilu.", + "Could not find category \"%s\"" : "Nun pudo alcontrase la estaya \"%s.\"", + "Sunday" : "Domingu", + "Monday" : "Llunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Xueves", + "Friday" : "Vienres", + "Saturday" : "Sábadu", + "Sun." : "Dom.", + "Mon." : "Llu.", + "Tue." : "Mar.", + "Wed." : "Mié.", + "Thu." : "Xue.", + "Fri." : "Vie.", + "Sat." : "Sáb.", + "Su" : "Do", + "Mo" : "Ll", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Xu", + "Fr" : "Vi", + "Sa" : "Sá", + "January" : "Xineru", + "February" : "Febreru", + "March" : "Marzu", + "April" : "Abril", + "May" : "Mayu", + "June" : "Xunu", + "July" : "Xunetu", + "August" : "Agostu", + "September" : "Setiembre", + "October" : "Ochobre", + "November" : "Payares", + "December" : "Avientu", + "Jan." : "Xin.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Xun.", + "Jul." : "Xnt.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Och.", + "Nov." : "Pay.", + "Dec." : "Avi.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Namái tan permitíos los siguientes caráuteres nun nome d'usuariu: \"a-z\", \"A-Z\", \"0-9\", y \"_.@-'\"", + "A valid username must be provided" : "Tien d'apurrise un nome d'usuariu válidu", + "Username contains whitespace at the beginning or at the end" : "El nome d'usuario contién espacios en blancu al entamu o al final", + "Username must not consist of dots only" : "El nome d'usuariu nun pue tener puntos", + "A valid password must be provided" : "Tien d'apurrise una contraseña válida", + "The username is already being used" : "El nome d'usuariu yá ta usándose", + "User disabled" : "Usuariu desactiváu", + "Login canceled by app" : "Aniciar sesión canceláu pola aplicación", + "a safe home for all your data" : "un llar seguru pa tolos tos datos", + "File is currently busy, please try again later" : "Fichaeru ta ocupáu, por favor intentelo de nuevu más tarde", + "Can't read file" : "Nun ye a lleese'l ficheru", + "Application is not enabled" : "L'aplicación nun ta habilitada", + "Authentication error" : "Fallu d'autenticación", + "Token expired. Please reload page." : "Token caducáu. Recarga la páxina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nun hai controladores de bases de datos (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory" : "Nun pue escribise nel direutoriu \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Esto davezu íguase dando'l permisu d'escritura nel direutoriu de configuración al sirvidor web. Mira %s", + "Cannot write into \"apps\" directory" : "Nun pue escribise nel direutoriu \"apps\"", + "Cannot create \"data\" directory" : "Nun pue crease'l direutoriu «datos»", + "Setting locale to %s failed" : "Falló l'activación del idioma %s", + "Please install one of these locales on your system and restart your webserver." : "Instala ún d'estos locales nel to sistema y reanicia'l sirvidor web", + "PHP module %s not installed." : "Nun ta instaláu'l módulu PHP %s", + "Please ask your server administrator to install the module." : "Por favor, entrúga-y al to alministrador del sirvidor pa instalar el módulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "La configuración de PHP \"%s\" nun s'afita \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload afita \"%s\" en llugar del valor esperáu \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pa solucionar esti problema definíu mbstring.func_overloada 0 nel so php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ríquese siquier. Anguaño ta instaláu %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pa solucionar esti problema actualiza latso versión de libxml2 y reanicia'l to sirvidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ta aparentemente configuráu pa desaniciar bloques de documentos en llinia. Esto va facer que delles aplicaciones principales nun tean accesibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dablemente esto seya culpa d'un caché o acelerador, como por exemplu Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse los módulos PHP, ¿pero tán entá llistaos como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor, entruga al to alministrador pa reaniciar el sirvidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requeríu", + "Please upgrade your database version" : "Por favor, anueva la versión de la to base de datos", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, camuda los permisos a 0770 pa que'l direutoriu nun pueda llistase por otros usuarios.", + "Check the value of \"datadirectory\" in your configuration" : "Comprobar el valor del \"datadirectory\" na so configuración", + "Your data directory is invalid" : "El to direutoriu de datos nun ye válidu", + "Could not obtain lock type %d on \"%s\"." : "Nun pudo facese'l bloquéu %d en \"%s\".", + "Storage unauthorized. %s" : "Almacenamientu desautorizáu. %s", + "Storage incomplete configuration. %s" : "Configuración d'almacenamientu incompleta. %s", + "Storage connection error. %s" : "Fallu de conexón al almacenamientu. %s", + "Storage is temporarily not available" : "L'almacenamientu ta temporalmente non disponible", + "Storage connection timeout. %s" : "Tiempu escosao de conexón al almacenamientu. %s", + "Following databases are supported: %s" : "Les siguientes bases de datos tan sofitaes: %s", + "Following platforms are supported: %s" : "Les siguientes plataformes tan sofitaes: %s", + "Basic settings" : "Axustes básicos", + "Sharing" : "Compartiendo", + "Security" : "Seguranza", + "Personal info" : "Información personal" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ast.json b/docker/overlays/nextcloud/html/lib/l10n/ast.json new file mode 100644 index 0000000..c0c7abc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ast.json @@ -0,0 +1,168 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡Nun pue escribise nel direutoriu «config»!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Davezu esto pue iguase dándo-y al sirvidor web accesu d'escritura al direutoriu de configuración", + "See %s" : "Mira %s", + "Sample configuration detected" : "Configuración d'amuesa detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectose que la configuración d'amuesa copiose. Esto pue encaboxar la instalación y dexala ensín soporte. Llee la documentación enantes de facer cambéos en config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "Education Edition" : "Edición educativa", + "Enterprise bundle" : "Llote empresarial", + "Social sharing bundle" : "Llote de compartición social", + "PHP %s or higher is required." : "Necesítase PHP %s o superior", + "PHP with a version lower than %s is required." : "Necesítase una versión PHP anterior a %s", + "%sbit or higher PHP required." : "Necesítase PHP %sbit o superior", + "The command line tool %s could not be found" : "La ferramienta línea de comandu %s nun pudo alcontrase", + "The library %s is not available." : "La librería %s nun ta disponible", + "Authentication" : "Autenticación", + "Unknown filetype" : "Triba de ficheru desconocida", + "Invalid image" : "Imaxe inválida", + "Avatar image is not square" : "La imaxe del avatar nun ye cuadrada", + "today" : "güei", + "tomorrow" : "mañana", + "yesterday" : "ayeri", + "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n díes"], + "last month" : "mes caberu", + "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"], + "last year" : "añu caberu", + "_%n year ago_::_%n years ago_" : ["hai %n añu","hai %n años"], + "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n hores"], + "_%n minute ago_::_%n minutes ago_" : ["hai %n minutu","hai %n minutos"], + "seconds ago" : "hai segundos", + "File name is a reserved word" : "El nome de ficheru ye una pallabra reservada", + "File name contains at least one invalid character" : "El nome del ficheru contién polo menos un carácter non válidu", + "File name is too long" : "El nome de ficheru ye demasiáu llargu", + "Empty filename is not allowed" : "Nun s'almite un nome de ficheru baleru", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicación \"%s\" nun puede instalase porque nun se llee'l ficheru appinfo.", + "__language_name__" : "Asturianu", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Settings", + "Log out" : "Zarrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Usuariu desconocíu", + "Additional settings" : "Axustes adicionales", + "%s enter the database username and name." : "%s introducir el nome d'usuariu y el nome de la base de datos .", + "%s enter the database username." : "%s introducir l'usuariu de la base de datos.", + "%s enter the database name." : "%s introducir nome de la base de datos.", + "%s you may not use dots in the database name" : "%s nun pues usar puntos nel nome de la base de datos", + "You need to enter details of an existing account." : "Precises introducir los detalles d'una cuenta esistente.", + "Oracle connection could not be established" : "Nun pudo afitase la conexón d'Oracle", + "Oracle username and/or password not valid" : "Nome d'usuariu o contraseña d'Oracle non válidos", + "PostgreSQL username and/or password not valid" : "Nome d'usuariu o contraseña PostgreSQL non válidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nun ta sofitáu y %s nun furrulará afayadizamente nesta plataforma. ¡Úsalu baxo'l to riesgu!", + "For the best results, please consider using a GNU/Linux server instead." : "Pa los meyores resultaos, por favor considera l'usu d'un sirvidor GNU/Linux nel so llugar.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Paez ser que la instancia %s ta executándose nun entornu de PHP 32 bits y el open_basedir configuróse en php.ini. Esto va dar llugar a problemes colos ficheros de más de 4 GB y nun ye nada recomendable.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, desanicia la configuración open_basedir dientro la so php.ini o camude a PHP 64 bits.", + "Set an admin username." : "Afitar nome d'usuariu p'almin", + "Set an admin password." : "Afitar contraseña p'almin", + "Can't create or write into the data directory %s" : "Nun pue crease o escribir dientro los datos del direutoriu %s", + "Invalid Federated Cloud ID" : "ID non válida de ñube federada", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartíu %s tien d'implementar la interfaz OCP\\Share_Backend", + "Sharing backend %s not found" : "Nun s'alcontró'l botón de compartición %s", + "Sharing backend for %s not found" : "Nun s'alcontró'l botón de partición pa %s", + "Open »%s«" : "Abrir «%s»", + "You are not allowed to share %s" : "Nun tienes permisu pa compartir %s", + "Can’t increase permissions of %s" : "Nun pue aumentase los permisos de %s", + "Files can’t be shared with delete permissions" : "Los ficheros nun puen compartise colos permisos de desaniciu", + "Files can’t be shared with create permissions" : "Los ficheros nun puen compartise colos permisos de creación", + "Expiration date is in the past" : "La data de caducidá ta nel pasáu.", + "Can’t set expiration date more than %s days in the future" : "Nun pue afitase la data de caducidá más de %s díes nel futuru", + "Click the button below to open it." : "Primi'l botón d'embaxo p'abrilu.", + "Could not find category \"%s\"" : "Nun pudo alcontrase la estaya \"%s.\"", + "Sunday" : "Domingu", + "Monday" : "Llunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Xueves", + "Friday" : "Vienres", + "Saturday" : "Sábadu", + "Sun." : "Dom.", + "Mon." : "Llu.", + "Tue." : "Mar.", + "Wed." : "Mié.", + "Thu." : "Xue.", + "Fri." : "Vie.", + "Sat." : "Sáb.", + "Su" : "Do", + "Mo" : "Ll", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Xu", + "Fr" : "Vi", + "Sa" : "Sá", + "January" : "Xineru", + "February" : "Febreru", + "March" : "Marzu", + "April" : "Abril", + "May" : "Mayu", + "June" : "Xunu", + "July" : "Xunetu", + "August" : "Agostu", + "September" : "Setiembre", + "October" : "Ochobre", + "November" : "Payares", + "December" : "Avientu", + "Jan." : "Xin.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Xun.", + "Jul." : "Xnt.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Och.", + "Nov." : "Pay.", + "Dec." : "Avi.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Namái tan permitíos los siguientes caráuteres nun nome d'usuariu: \"a-z\", \"A-Z\", \"0-9\", y \"_.@-'\"", + "A valid username must be provided" : "Tien d'apurrise un nome d'usuariu válidu", + "Username contains whitespace at the beginning or at the end" : "El nome d'usuario contién espacios en blancu al entamu o al final", + "Username must not consist of dots only" : "El nome d'usuariu nun pue tener puntos", + "A valid password must be provided" : "Tien d'apurrise una contraseña válida", + "The username is already being used" : "El nome d'usuariu yá ta usándose", + "User disabled" : "Usuariu desactiváu", + "Login canceled by app" : "Aniciar sesión canceláu pola aplicación", + "a safe home for all your data" : "un llar seguru pa tolos tos datos", + "File is currently busy, please try again later" : "Fichaeru ta ocupáu, por favor intentelo de nuevu más tarde", + "Can't read file" : "Nun ye a lleese'l ficheru", + "Application is not enabled" : "L'aplicación nun ta habilitada", + "Authentication error" : "Fallu d'autenticación", + "Token expired. Please reload page." : "Token caducáu. Recarga la páxina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nun hai controladores de bases de datos (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory" : "Nun pue escribise nel direutoriu \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Esto davezu íguase dando'l permisu d'escritura nel direutoriu de configuración al sirvidor web. Mira %s", + "Cannot write into \"apps\" directory" : "Nun pue escribise nel direutoriu \"apps\"", + "Cannot create \"data\" directory" : "Nun pue crease'l direutoriu «datos»", + "Setting locale to %s failed" : "Falló l'activación del idioma %s", + "Please install one of these locales on your system and restart your webserver." : "Instala ún d'estos locales nel to sistema y reanicia'l sirvidor web", + "PHP module %s not installed." : "Nun ta instaláu'l módulu PHP %s", + "Please ask your server administrator to install the module." : "Por favor, entrúga-y al to alministrador del sirvidor pa instalar el módulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "La configuración de PHP \"%s\" nun s'afita \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload afita \"%s\" en llugar del valor esperáu \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pa solucionar esti problema definíu mbstring.func_overloada 0 nel so php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ríquese siquier. Anguaño ta instaláu %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pa solucionar esti problema actualiza latso versión de libxml2 y reanicia'l to sirvidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ta aparentemente configuráu pa desaniciar bloques de documentos en llinia. Esto va facer que delles aplicaciones principales nun tean accesibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dablemente esto seya culpa d'un caché o acelerador, como por exemplu Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse los módulos PHP, ¿pero tán entá llistaos como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor, entruga al to alministrador pa reaniciar el sirvidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requeríu", + "Please upgrade your database version" : "Por favor, anueva la versión de la to base de datos", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, camuda los permisos a 0770 pa que'l direutoriu nun pueda llistase por otros usuarios.", + "Check the value of \"datadirectory\" in your configuration" : "Comprobar el valor del \"datadirectory\" na so configuración", + "Your data directory is invalid" : "El to direutoriu de datos nun ye válidu", + "Could not obtain lock type %d on \"%s\"." : "Nun pudo facese'l bloquéu %d en \"%s\".", + "Storage unauthorized. %s" : "Almacenamientu desautorizáu. %s", + "Storage incomplete configuration. %s" : "Configuración d'almacenamientu incompleta. %s", + "Storage connection error. %s" : "Fallu de conexón al almacenamientu. %s", + "Storage is temporarily not available" : "L'almacenamientu ta temporalmente non disponible", + "Storage connection timeout. %s" : "Tiempu escosao de conexón al almacenamientu. %s", + "Following databases are supported: %s" : "Les siguientes bases de datos tan sofitaes: %s", + "Following platforms are supported: %s" : "Les siguientes plataformes tan sofitaes: %s", + "Basic settings" : "Axustes básicos", + "Sharing" : "Compartiendo", + "Security" : "Seguranza", + "Personal info" : "Información personal" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/az.js b/docker/overlays/nextcloud/html/lib/l10n/az.js new file mode 100644 index 0000000..4a06383 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/az.js @@ -0,0 +1,71 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"configurasiya\" direktoriyasının daxilində yazmaq mümkün deyil", + "This can usually be fixed by giving the webserver write access to the config directory" : "Adətən tez həll etmək üçün WEB serverdə yazma yetkisi verilir", + "See %s" : "Bax %s", + "Sample configuration detected" : "Konfiqurasiya nüsxəsi təyin edildi", + "Unknown filetype" : "Fayl tipi bəlli deyil.", + "Invalid image" : "Yalnış şəkil", + "today" : "Bu gün", + "seconds ago" : "saniyələr öncə", + "__language_name__" : "Azərbaycan dili", + "Help" : "Kömək", + "Settings" : "Quraşdırmalar", + "Users" : "İstifadəçilər", + "Unknown user" : "Istifadəçi tanınmır ", + "%s enter the database username." : "Verilənlər bazası istifadəçi adını %s daxil et.", + "%s enter the database name." : "Verilənlər bazası adını %s daxil et.", + "Oracle connection could not be established" : "Oracle qoşulması alınmır", + "Oracle username and/or password not valid" : "Oracle istifadəçi adı və/ya şifrəsi düzgün deyil", + "Set an admin username." : "İnzibatçı istifadəçi adını təyin et.", + "Set an admin password." : "İnzibatçı şifrəsini təyin et.", + "You are not allowed to share %s" : "%s-in yayimlanmasına sizə izin verilmir", + "Sunday" : "Bazar", + "Monday" : "Bazar ertəsi", + "Tuesday" : "Çərşənbə axşamı", + "Wednesday" : "Çərşənbə", + "Thursday" : "Cümə axşamı", + "Friday" : "Cümə", + "Saturday" : "Şənbə", + "Sun." : "Baz.", + "Mon." : "Ber.", + "Tue." : "Çax.", + "Wed." : "Çər.", + "Thu." : "Cax.", + "Fri." : "Cüm.", + "Sat." : "Şnb.", + "January" : "Yanvar", + "February" : "Fevral", + "March" : "Mart", + "April" : "Aprel", + "May" : "May", + "June" : "İyun", + "July" : "İyul", + "August" : "Avqust", + "September" : "Sentyabr", + "October" : "Oktyabr", + "November" : "Noyabr.", + "December" : "Dekabr", + "Jan." : "Yan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "May.", + "Jun." : "İyn.", + "Jul." : "İyl.", + "Aug." : "Avq.", + "Sep." : "Sen.", + "Oct." : "Okt.", + "Nov." : "Noy.", + "Dec." : "Dek.", + "A valid username must be provided" : "Düzgün istifadəçi adı daxil edilməlidir", + "A valid password must be provided" : "Düzgün şifrə daxil edilməlidir", + "Application is not enabled" : "Proqram təminatı aktiv edilməyib", + "Authentication error" : "Təyinat metodikası", + "Token expired. Please reload page." : "Token vaxtı bitib. Xahiş olunur səhifəni yenidən yükləyəsiniz.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Bu ola bilər ki, cache/accelerator such tərəfindən cağırılıb hansi ki, Zend OPcache və eAccelerator-da olduğu kimidir.", + "Sharing" : "Paylaşılır", + "Personal info" : "Şəxsi məlumat" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/az.json b/docker/overlays/nextcloud/html/lib/l10n/az.json new file mode 100644 index 0000000..f2ff587 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/az.json @@ -0,0 +1,69 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"configurasiya\" direktoriyasının daxilində yazmaq mümkün deyil", + "This can usually be fixed by giving the webserver write access to the config directory" : "Adətən tez həll etmək üçün WEB serverdə yazma yetkisi verilir", + "See %s" : "Bax %s", + "Sample configuration detected" : "Konfiqurasiya nüsxəsi təyin edildi", + "Unknown filetype" : "Fayl tipi bəlli deyil.", + "Invalid image" : "Yalnış şəkil", + "today" : "Bu gün", + "seconds ago" : "saniyələr öncə", + "__language_name__" : "Azərbaycan dili", + "Help" : "Kömək", + "Settings" : "Quraşdırmalar", + "Users" : "İstifadəçilər", + "Unknown user" : "Istifadəçi tanınmır ", + "%s enter the database username." : "Verilənlər bazası istifadəçi adını %s daxil et.", + "%s enter the database name." : "Verilənlər bazası adını %s daxil et.", + "Oracle connection could not be established" : "Oracle qoşulması alınmır", + "Oracle username and/or password not valid" : "Oracle istifadəçi adı və/ya şifrəsi düzgün deyil", + "Set an admin username." : "İnzibatçı istifadəçi adını təyin et.", + "Set an admin password." : "İnzibatçı şifrəsini təyin et.", + "You are not allowed to share %s" : "%s-in yayimlanmasına sizə izin verilmir", + "Sunday" : "Bazar", + "Monday" : "Bazar ertəsi", + "Tuesday" : "Çərşənbə axşamı", + "Wednesday" : "Çərşənbə", + "Thursday" : "Cümə axşamı", + "Friday" : "Cümə", + "Saturday" : "Şənbə", + "Sun." : "Baz.", + "Mon." : "Ber.", + "Tue." : "Çax.", + "Wed." : "Çər.", + "Thu." : "Cax.", + "Fri." : "Cüm.", + "Sat." : "Şnb.", + "January" : "Yanvar", + "February" : "Fevral", + "March" : "Mart", + "April" : "Aprel", + "May" : "May", + "June" : "İyun", + "July" : "İyul", + "August" : "Avqust", + "September" : "Sentyabr", + "October" : "Oktyabr", + "November" : "Noyabr.", + "December" : "Dekabr", + "Jan." : "Yan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "May.", + "Jun." : "İyn.", + "Jul." : "İyl.", + "Aug." : "Avq.", + "Sep." : "Sen.", + "Oct." : "Okt.", + "Nov." : "Noy.", + "Dec." : "Dek.", + "A valid username must be provided" : "Düzgün istifadəçi adı daxil edilməlidir", + "A valid password must be provided" : "Düzgün şifrə daxil edilməlidir", + "Application is not enabled" : "Proqram təminatı aktiv edilməyib", + "Authentication error" : "Təyinat metodikası", + "Token expired. Please reload page." : "Token vaxtı bitib. Xahiş olunur səhifəni yenidən yükləyəsiniz.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Bu ola bilər ki, cache/accelerator such tərəfindən cağırılıb hansi ki, Zend OPcache və eAccelerator-da olduğu kimidir.", + "Sharing" : "Paylaşılır", + "Personal info" : "Şəxsi məlumat" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/be.js b/docker/overlays/nextcloud/html/lib/l10n/be.js new file mode 100644 index 0000000..edc5bb7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/be.js @@ -0,0 +1,34 @@ +OC.L10N.register( + "lib", + { + "__language_name__" : "Беларуская", + "Help" : "Help", + "Settings" : "Налады", + "Sunday" : "Нядзеля", + "Monday" : "Панядзелак", + "Tuesday" : "Аўторак", + "Wednesday" : "Серада", + "Thursday" : "Чацвер", + "Friday" : "Пятніца", + "Saturday" : "Субота", + "Sun." : "Няд.", + "Mon." : "Пн.", + "Tue." : "Аўт.", + "Wed." : "Ср.", + "Thu." : "Чац.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "January" : "Студзень", + "February" : "Люты", + "March" : "Сакавік", + "April" : "Красавік", + "May" : "Май", + "June" : "Чэрвень", + "July" : "Ліпень", + "August" : "Жнівень", + "September" : "Верасень", + "October" : "Кастрычнік", + "November" : "Лістапад", + "December" : "Снежань" +}, +"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/be.json b/docker/overlays/nextcloud/html/lib/l10n/be.json new file mode 100644 index 0000000..1603228 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/be.json @@ -0,0 +1,32 @@ +{ "translations": { + "__language_name__" : "Беларуская", + "Help" : "Help", + "Settings" : "Налады", + "Sunday" : "Нядзеля", + "Monday" : "Панядзелак", + "Tuesday" : "Аўторак", + "Wednesday" : "Серада", + "Thursday" : "Чацвер", + "Friday" : "Пятніца", + "Saturday" : "Субота", + "Sun." : "Няд.", + "Mon." : "Пн.", + "Tue." : "Аўт.", + "Wed." : "Ср.", + "Thu." : "Чац.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "January" : "Студзень", + "February" : "Люты", + "March" : "Сакавік", + "April" : "Красавік", + "May" : "Май", + "June" : "Чэрвень", + "July" : "Ліпень", + "August" : "Жнівень", + "September" : "Верасень", + "October" : "Кастрычнік", + "November" : "Лістапад", + "December" : "Снежань" +},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/bg.js b/docker/overlays/nextcloud/html/lib/l10n/bg.js new file mode 100644 index 0000000..e718afd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bg.js @@ -0,0 +1,144 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Неуспешен опит за запис в \"config\" папката!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Това може да бъде решено единствено като разрешиш на уеб сървъра да пише в config папката.", + "See %s" : "Вижте %s", + "Sample configuration detected" : "Открита е примерна конфигурация", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Усетено беше че примерната конфигурация е копирана. Това може да развли инсталацията ти и не се поддържа. Моля, прочети документацията преди да правиш промени на config.php", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "PHP %s or higher is required." : "Изисква се PHP %s или по-нова.", + "PHP with a version lower than %s is required." : "Необходим е PHP с версия по-ниска от %s.", + "The command line tool %s could not be found" : "Конзолната команда %s не може да бъде намерена", + "The library %s is not available." : "Библиотеката %s не е налична", + "Authentication" : "Удостоверяване", + "Unknown filetype" : "Непознат тип файл", + "Invalid image" : "Невалидно изображение.", + "today" : "днес", + "tomorrow" : "утре", + "yesterday" : "вчера", + "_%n day ago_::_%n days ago_" : ["преди %n ден","преди %n дни"], + "next month" : "следващия месец", + "last month" : "миналия месец", + "_%n month ago_::_%n months ago_" : ["преди %n месец","преди %n месеца"], + "next year" : "следващата година", + "last year" : "миналата година", + "_%n year ago_::_%n years ago_" : ["преди %n година","преди %n години"], + "_%n hour ago_::_%n hours ago_" : ["преди %n час","преди %n часа"], + "_%n minute ago_::_%n minutes ago_" : ["преди %n минута","преди %n минути"], + "seconds ago" : "преди секунди", + "File name contains at least one invalid character" : "Името на файла съдържа поне един невалиден символ", + "File name is too long" : "Името на файла е твърде дълго", + "__language_name__" : "Български", + "This is an automatically sent email, please do not reply." : "Имейлът е генериран автоматично, моля не отговаряйте.", + "Help" : "Помощ", + "Apps" : "Приложения", + "Settings" : "Настройки", + "Log out" : "Отписване", + "Users" : "Потребители", + "Unknown user" : "Непознат потребител", + "Additional settings" : "Допълнителни настройки", + "%s enter the database username and name." : "%s въведете потребителско име и име за базата данни", + "%s enter the database username." : "%s въведете потребител за базата данни.", + "%s enter the database name." : "%s въведи име на базата данни.", + "%s you may not use dots in the database name" : "%s, не може да ползваш точки в името на базата данни.", + "Oracle username and/or password not valid" : "Невалидно Oracle потребителско име и/или парола.", + "PostgreSQL username and/or password not valid" : "Невалидно PostgreSQL потребителско име и/или парола.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не се подържа и %s няма да работи правилно на тази платформа. Използвайте го на свой собствен риск!", + "For the best results, please consider using a GNU/Linux server instead." : "За най-добри резултати, моля, помисли дали не бихте желали да използваште GNU/Linux сървър.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Моля, премахтене настройката за open_basedir от вашия php.ini или преминете към 64-битово PHP.", + "Set an admin username." : "Задайте потребителско име за администратор.", + "Set an admin password." : "Задай парола за администратор.", + "Can't create or write into the data directory %s" : "Неуспешно създаване или записване в \"data\" папката %s", + "Invalid Federated Cloud ID" : "Невалиден Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Споделянето на сървърния %s трябва да поддържа OCP\\Share_Backend интерфейс.", + "Sharing backend %s not found" : "Споделянето на сървърния %s не е открито.", + "Sharing backend for %s not found" : "Споделянето на сървъра за %s не е открито.", + "Open »%s«" : "Отвори »%s«", + "You are not allowed to share %s" : "Не ти е разрешено да споделяш %s.", + "Can’t set expiration date more than %s days in the future" : "Неуспешно задаване на срок на валидност повече от %s дни в бъдещето", + "%1$s shared »%2$s« with you" : "%1$s сподели »%2$s« с вас", + "%1$s shared »%2$s« with you." : "%1$s сподели »%2$s« с вас.", + "Could not find category \"%s\"" : "Невъзможно откриване на категорията \"%s\".", + "Sunday" : "неделя", + "Monday" : "понеделник", + "Tuesday" : "вторник", + "Wednesday" : "сряда", + "Thursday" : "четвъртък", + "Friday" : "петък", + "Saturday" : "събота", + "Sun." : "нед", + "Mon." : "пон", + "Tue." : "вт", + "Wed." : "ср", + "Thu." : "чет", + "Fri." : "пет", + "Sat." : "съб", + "Su" : "нд", + "Mo" : "пн", + "Tu" : "вт", + "We" : "ср", + "Th" : "чт", + "Fr" : "пт", + "Sa" : "сб", + "January" : "януари", + "February" : "февруару", + "March" : "март", + "April" : "април", + "May" : "май", + "June" : "юни", + "July" : "юли", + "August" : "август", + "September" : "септември", + "October" : "октомври", + "November" : "ноември", + "December" : "декември", + "Jan." : "яну", + "Feb." : "фев", + "Mar." : "мар", + "Apr." : "апр", + "May." : "май", + "Jun." : "юни", + "Jul." : "юли", + "Aug." : "авг", + "Sep." : "сеп", + "Oct." : "окт", + "Nov." : "ное", + "Dec." : "дек", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Потребителските имена може да съдържат следните знаци: \"a-z\", \"A-Z\", \"0-9\" и \"_.@-'\"", + "A valid username must be provided" : "Трябва да въведете валидно потребителско.", + "Username contains whitespace at the beginning or at the end" : "Потребителското име започва или завършва с интервал.", + "A valid password must be provided" : "Трябва да въведете валидна парола.", + "The username is already being used" : "Потребителското име е вече заето.", + "User disabled" : "Потребителят е деактивиран", + "a safe home for all your data" : "безопасен дом за всички ваши данни", + "Can't read file" : "Файлът не може да бъде прочетен", + "Application is not enabled" : "Приложението не е включено", + "Authentication error" : "Грешка при удостоверяването", + "Token expired. Please reload page." : "Изтекла сесия. Моля, презареди страницата.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Липсват инсталирани драйвери за бази данни(sqlite, mysql или postgresql).", + "Cannot write into \"config\" directory" : "Неуспешен опит за запис в \"config\" папката.", + "Cannot write into \"apps\" directory" : "Писането в папка приложения не е възможно", + "Setting locale to %s failed" : "Неуспешно задаване на %s като настройка език-държава.", + "Please install one of these locales on your system and restart your webserver." : "Моля, инсталирай едно от следните език-държава на сървъра и рестартирай уеб сървъра.", + "PHP module %s not installed." : "PHP модулът %s не е инсталиран.", + "Please ask your server administrator to install the module." : "Моля, помолете вашия администратор да инсталира модула.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Това може да се дължи на cache/accelerator като Zend OPache или eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP модулите са инсталирани, но все още се обявяват като липсващи?", + "Please ask your server administrator to restart the web server." : "Моля, поискай от своя администратор да рестартира уеб сървъра.", + "PostgreSQL >= 9 required" : "Изисква се PostgreSQL >= 9", + "Please upgrade your database version" : "Моля, обнови базата данни.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Моля, променете правата за достъп на 0770, за да не може директорията да бъде видяна от други потребители.", + "Could not obtain lock type %d on \"%s\"." : "Неуспешен опит за ексклузивен достъп от типa %d върху \"%s\".", + "Storage is temporarily not available" : "Временно хранилището не е налично", + "Following databases are supported: %s" : "Следните бази данни са поддържани: %s", + "Following platforms are supported: %s" : "Поддържани са следните платформи: %s", + "Basic settings" : "Основни настройки", + "Sharing" : "Споделяне", + "Security" : "Сигурност", + "Personal info" : "Лични данни" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/bg.json b/docker/overlays/nextcloud/html/lib/l10n/bg.json new file mode 100644 index 0000000..dcc611d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bg.json @@ -0,0 +1,142 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Неуспешен опит за запис в \"config\" папката!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Това може да бъде решено единствено като разрешиш на уеб сървъра да пише в config папката.", + "See %s" : "Вижте %s", + "Sample configuration detected" : "Открита е примерна конфигурация", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Усетено беше че примерната конфигурация е копирана. Това може да развли инсталацията ти и не се поддържа. Моля, прочети документацията преди да правиш промени на config.php", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "PHP %s or higher is required." : "Изисква се PHP %s или по-нова.", + "PHP with a version lower than %s is required." : "Необходим е PHP с версия по-ниска от %s.", + "The command line tool %s could not be found" : "Конзолната команда %s не може да бъде намерена", + "The library %s is not available." : "Библиотеката %s не е налична", + "Authentication" : "Удостоверяване", + "Unknown filetype" : "Непознат тип файл", + "Invalid image" : "Невалидно изображение.", + "today" : "днес", + "tomorrow" : "утре", + "yesterday" : "вчера", + "_%n day ago_::_%n days ago_" : ["преди %n ден","преди %n дни"], + "next month" : "следващия месец", + "last month" : "миналия месец", + "_%n month ago_::_%n months ago_" : ["преди %n месец","преди %n месеца"], + "next year" : "следващата година", + "last year" : "миналата година", + "_%n year ago_::_%n years ago_" : ["преди %n година","преди %n години"], + "_%n hour ago_::_%n hours ago_" : ["преди %n час","преди %n часа"], + "_%n minute ago_::_%n minutes ago_" : ["преди %n минута","преди %n минути"], + "seconds ago" : "преди секунди", + "File name contains at least one invalid character" : "Името на файла съдържа поне един невалиден символ", + "File name is too long" : "Името на файла е твърде дълго", + "__language_name__" : "Български", + "This is an automatically sent email, please do not reply." : "Имейлът е генериран автоматично, моля не отговаряйте.", + "Help" : "Помощ", + "Apps" : "Приложения", + "Settings" : "Настройки", + "Log out" : "Отписване", + "Users" : "Потребители", + "Unknown user" : "Непознат потребител", + "Additional settings" : "Допълнителни настройки", + "%s enter the database username and name." : "%s въведете потребителско име и име за базата данни", + "%s enter the database username." : "%s въведете потребител за базата данни.", + "%s enter the database name." : "%s въведи име на базата данни.", + "%s you may not use dots in the database name" : "%s, не може да ползваш точки в името на базата данни.", + "Oracle username and/or password not valid" : "Невалидно Oracle потребителско име и/или парола.", + "PostgreSQL username and/or password not valid" : "Невалидно PostgreSQL потребителско име и/или парола.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не се подържа и %s няма да работи правилно на тази платформа. Използвайте го на свой собствен риск!", + "For the best results, please consider using a GNU/Linux server instead." : "За най-добри резултати, моля, помисли дали не бихте желали да използваште GNU/Linux сървър.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Моля, премахтене настройката за open_basedir от вашия php.ini или преминете към 64-битово PHP.", + "Set an admin username." : "Задайте потребителско име за администратор.", + "Set an admin password." : "Задай парола за администратор.", + "Can't create or write into the data directory %s" : "Неуспешно създаване или записване в \"data\" папката %s", + "Invalid Federated Cloud ID" : "Невалиден Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Споделянето на сървърния %s трябва да поддържа OCP\\Share_Backend интерфейс.", + "Sharing backend %s not found" : "Споделянето на сървърния %s не е открито.", + "Sharing backend for %s not found" : "Споделянето на сървъра за %s не е открито.", + "Open »%s«" : "Отвори »%s«", + "You are not allowed to share %s" : "Не ти е разрешено да споделяш %s.", + "Can’t set expiration date more than %s days in the future" : "Неуспешно задаване на срок на валидност повече от %s дни в бъдещето", + "%1$s shared »%2$s« with you" : "%1$s сподели »%2$s« с вас", + "%1$s shared »%2$s« with you." : "%1$s сподели »%2$s« с вас.", + "Could not find category \"%s\"" : "Невъзможно откриване на категорията \"%s\".", + "Sunday" : "неделя", + "Monday" : "понеделник", + "Tuesday" : "вторник", + "Wednesday" : "сряда", + "Thursday" : "четвъртък", + "Friday" : "петък", + "Saturday" : "събота", + "Sun." : "нед", + "Mon." : "пон", + "Tue." : "вт", + "Wed." : "ср", + "Thu." : "чет", + "Fri." : "пет", + "Sat." : "съб", + "Su" : "нд", + "Mo" : "пн", + "Tu" : "вт", + "We" : "ср", + "Th" : "чт", + "Fr" : "пт", + "Sa" : "сб", + "January" : "януари", + "February" : "февруару", + "March" : "март", + "April" : "април", + "May" : "май", + "June" : "юни", + "July" : "юли", + "August" : "август", + "September" : "септември", + "October" : "октомври", + "November" : "ноември", + "December" : "декември", + "Jan." : "яну", + "Feb." : "фев", + "Mar." : "мар", + "Apr." : "апр", + "May." : "май", + "Jun." : "юни", + "Jul." : "юли", + "Aug." : "авг", + "Sep." : "сеп", + "Oct." : "окт", + "Nov." : "ное", + "Dec." : "дек", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Потребителските имена може да съдържат следните знаци: \"a-z\", \"A-Z\", \"0-9\" и \"_.@-'\"", + "A valid username must be provided" : "Трябва да въведете валидно потребителско.", + "Username contains whitespace at the beginning or at the end" : "Потребителското име започва или завършва с интервал.", + "A valid password must be provided" : "Трябва да въведете валидна парола.", + "The username is already being used" : "Потребителското име е вече заето.", + "User disabled" : "Потребителят е деактивиран", + "a safe home for all your data" : "безопасен дом за всички ваши данни", + "Can't read file" : "Файлът не може да бъде прочетен", + "Application is not enabled" : "Приложението не е включено", + "Authentication error" : "Грешка при удостоверяването", + "Token expired. Please reload page." : "Изтекла сесия. Моля, презареди страницата.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Липсват инсталирани драйвери за бази данни(sqlite, mysql или postgresql).", + "Cannot write into \"config\" directory" : "Неуспешен опит за запис в \"config\" папката.", + "Cannot write into \"apps\" directory" : "Писането в папка приложения не е възможно", + "Setting locale to %s failed" : "Неуспешно задаване на %s като настройка език-държава.", + "Please install one of these locales on your system and restart your webserver." : "Моля, инсталирай едно от следните език-държава на сървъра и рестартирай уеб сървъра.", + "PHP module %s not installed." : "PHP модулът %s не е инсталиран.", + "Please ask your server administrator to install the module." : "Моля, помолете вашия администратор да инсталира модула.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Това може да се дължи на cache/accelerator като Zend OPache или eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP модулите са инсталирани, но все още се обявяват като липсващи?", + "Please ask your server administrator to restart the web server." : "Моля, поискай от своя администратор да рестартира уеб сървъра.", + "PostgreSQL >= 9 required" : "Изисква се PostgreSQL >= 9", + "Please upgrade your database version" : "Моля, обнови базата данни.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Моля, променете правата за достъп на 0770, за да не може директорията да бъде видяна от други потребители.", + "Could not obtain lock type %d on \"%s\"." : "Неуспешен опит за ексклузивен достъп от типa %d върху \"%s\".", + "Storage is temporarily not available" : "Временно хранилището не е налично", + "Following databases are supported: %s" : "Следните бази данни са поддържани: %s", + "Following platforms are supported: %s" : "Поддържани са следните платформи: %s", + "Basic settings" : "Основни настройки", + "Sharing" : "Споделяне", + "Security" : "Сигурност", + "Personal info" : "Лични данни" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/bn_BD.js b/docker/overlays/nextcloud/html/lib/l10n/bn_BD.js new file mode 100644 index 0000000..2008107 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bn_BD.js @@ -0,0 +1,66 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\" ডিরেক্টরিতে লেখা যায়না!", + "This can usually be fixed by giving the webserver write access to the config directory" : "সাধারণতঃ ওয়বসার্ভারকে কনফিগ ডিরেক্টরিতে লেখার অধিকার দিয়ে এই সমস্যা সমাধান করা যায়", + "See %s" : "%s দেখ", + "Sample configuration detected" : "নমুনা কনফিগারেশন পাওয়া গেছে", + "Unknown filetype" : "অজানা প্রকৃতির ফাইল", + "Invalid image" : "অবৈধ চিত্র", + "today" : "আজ", + "yesterday" : "গতকাল", + "last month" : "গত মাস", + "last year" : "গত বছর", + "seconds ago" : "সেকেন্ড পূর্বে", + "__language_name__" : "বাংলা ভাষা", + "Help" : "সহায়িকা", + "Apps" : "অ্যাপ", + "Settings" : "সেটিংস", + "Log out" : "প্রস্থান", + "Users" : "ব্যবহারকারী", + "Unknown user" : "অপরিচিত ব্যবহারকারী", + "You are not allowed to share %s" : "আপনি %s ভাগাভাগি করতে পারবেননা", + "Sunday" : "রবিবার", + "Monday" : "সোমবার", + "Tuesday" : "মঙ্গলবার", + "Wednesday" : "বুধবার", + "Thursday" : "বৃহস্পতিবার", + "Friday" : "শুক্রবার", + "Saturday" : "শনিবার", + "Sun." : "রবি.", + "Mon." : "সোম.", + "Tue." : "মঙ্গল.", + "Wed." : "বুধ.", + "Thu." : "বৃহঃ.", + "Fri." : "শুক্র.", + "Sat." : "শনি.", + "January" : "জানুয়ারি", + "February" : "ফেব্রুয়ারি", + "March" : "মার্চ", + "April" : "এপ্রিল", + "May" : "মে", + "June" : "জুন", + "July" : "জুলাই", + "August" : "অগাষ্ট", + "September" : "সেপ্টেম্বর", + "October" : "অক্টোবর", + "November" : "নভেম্বর", + "December" : "ডিসেম্বর", + "Jan." : "জানু.", + "Feb." : "ফেব্রু.", + "Mar." : "মার্চ.", + "Apr." : "এপ্রিল.", + "May." : "মে.", + "Jun." : "জুন.", + "Jul." : "জুলাই.", + "Aug." : "অগাস্ট.", + "Sep." : "সেপ্টে.", + "Oct." : "অক্টো.", + "Nov." : "নভে.", + "Dec." : "ডিসে.", + "Application is not enabled" : "অ্যাপ্লিকেসনটি সক্রিয় নয়", + "Authentication error" : "অনুমোদন ঘটিত সমস্যা", + "Token expired. Please reload page." : "টোকেন মেয়াদোত্তীর্ণ। দয়া করে পৃষ্ঠাটি পূনরায় লোড করুন।", + "Sharing" : "ভাগাভাগিরত" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/bn_BD.json b/docker/overlays/nextcloud/html/lib/l10n/bn_BD.json new file mode 100644 index 0000000..209cdc5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bn_BD.json @@ -0,0 +1,64 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\" ডিরেক্টরিতে লেখা যায়না!", + "This can usually be fixed by giving the webserver write access to the config directory" : "সাধারণতঃ ওয়বসার্ভারকে কনফিগ ডিরেক্টরিতে লেখার অধিকার দিয়ে এই সমস্যা সমাধান করা যায়", + "See %s" : "%s দেখ", + "Sample configuration detected" : "নমুনা কনফিগারেশন পাওয়া গেছে", + "Unknown filetype" : "অজানা প্রকৃতির ফাইল", + "Invalid image" : "অবৈধ চিত্র", + "today" : "আজ", + "yesterday" : "গতকাল", + "last month" : "গত মাস", + "last year" : "গত বছর", + "seconds ago" : "সেকেন্ড পূর্বে", + "__language_name__" : "বাংলা ভাষা", + "Help" : "সহায়িকা", + "Apps" : "অ্যাপ", + "Settings" : "সেটিংস", + "Log out" : "প্রস্থান", + "Users" : "ব্যবহারকারী", + "Unknown user" : "অপরিচিত ব্যবহারকারী", + "You are not allowed to share %s" : "আপনি %s ভাগাভাগি করতে পারবেননা", + "Sunday" : "রবিবার", + "Monday" : "সোমবার", + "Tuesday" : "মঙ্গলবার", + "Wednesday" : "বুধবার", + "Thursday" : "বৃহস্পতিবার", + "Friday" : "শুক্রবার", + "Saturday" : "শনিবার", + "Sun." : "রবি.", + "Mon." : "সোম.", + "Tue." : "মঙ্গল.", + "Wed." : "বুধ.", + "Thu." : "বৃহঃ.", + "Fri." : "শুক্র.", + "Sat." : "শনি.", + "January" : "জানুয়ারি", + "February" : "ফেব্রুয়ারি", + "March" : "মার্চ", + "April" : "এপ্রিল", + "May" : "মে", + "June" : "জুন", + "July" : "জুলাই", + "August" : "অগাষ্ট", + "September" : "সেপ্টেম্বর", + "October" : "অক্টোবর", + "November" : "নভেম্বর", + "December" : "ডিসেম্বর", + "Jan." : "জানু.", + "Feb." : "ফেব্রু.", + "Mar." : "মার্চ.", + "Apr." : "এপ্রিল.", + "May." : "মে.", + "Jun." : "জুন.", + "Jul." : "জুলাই.", + "Aug." : "অগাস্ট.", + "Sep." : "সেপ্টে.", + "Oct." : "অক্টো.", + "Nov." : "নভে.", + "Dec." : "ডিসে.", + "Application is not enabled" : "অ্যাপ্লিকেসনটি সক্রিয় নয়", + "Authentication error" : "অনুমোদন ঘটিত সমস্যা", + "Token expired. Please reload page." : "টোকেন মেয়াদোত্তীর্ণ। দয়া করে পৃষ্ঠাটি পূনরায় লোড করুন।", + "Sharing" : "ভাগাভাগিরত" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/br.js b/docker/overlays/nextcloud/html/lib/l10n/br.js new file mode 100644 index 0000000..2b64310 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/br.js @@ -0,0 +1,55 @@ +OC.L10N.register( + "lib", + { + "See %s" : "Sellet %s", + "Other activities" : "Oberiantizoù all", + "PHP %s or higher is required." : "PHP %s pe hueloc'h a zo ret kaout.", + "PHP with a version lower than %s is required." : "PHP gant ur stumm izeloc'h eget %s a zo ret kaout.", + "The library %s is not available." : "Al levraoueg %s ne c'hell ket bezhgañ implijet", + "Server version %s or higher is required." : "Stumm servijour %s pe hueloc'h rekis", + "Unknown filetype" : "N'eo ket anavezet stumm an teuliad", + "Invalid image" : "N'eo ket aotreet ar skeudenn", + "today" : "hiziv", + "yesterday" : "dec'h", + "_%n day ago_::_%n days ago_" : ["%n deiz zo","%n deiz zo","%n deiz zo","%n deiz zo","%n deiz zo"], + "last month" : "ar miz tremenet", + "_%n month ago_::_%n months ago_" : ["%n miz zo","%n miz zo","%n miz zo","%n miz zo","%n miz zo"], + "last year" : "Ar bloaz tremenet", + "_%n year ago_::_%n years ago_" : ["%n bloaz zo","%n bloaz zo","%n bloaz zo","%n bloaz zo","%n bloaz zo"], + "_%n hour ago_::_%n hours ago_" : ["%n hervez zo","%n hervez zo","%n hervez zo","%n hervez zo","%n hervez zo"], + "_%n minute ago_::_%n minutes ago_" : ["%n munudenn-zo","%n munudenn-zo","%n munudenn-zo","%n munudenn-zo","%n munudenn-zo"], + "seconds ago" : "eilenn zo", + "File name contains at least one invalid character" : "Un arouez fall ez eus d'an neubeutañ en anv restr", + "File name is too long" : "Anv ar restr a zo re hir", + "Empty filename is not allowed" : "Un anv-restr goulo n'eo ket aotreet", + "__language_name__" : "Brezhoneg", + "Help" : "Skoazell", + "Apps" : "Meziant", + "Settings" : "Arventennoù", + "Log out" : "Kuitat", + "Users" : "Implijer", + "Unknown user" : "Implijer dianv", + "Additional settings" : "Stummoù ouzhpenn", + "Set an admin username." : "Lakaat un anv-impljer merour.", + "Set an admin password." : "Lakaat ur ger-tremenn merour.", + "Sharing backend for %s not found" : "Rannadenn backend evit %s n'eo ket bet kavet", + "Open »%s«" : "Digeriñ »%s«", + "The username is already being used" : "An anv-implijet a zo dija implijet", + "User disabled" : "Implijer disaotreet", + "Login canceled by app" : "Mont tre arrestet gant ar meziant", + "Can't read file" : "N'eo ket posupl lenn ar restr", + "Application is not enabled" : "N'eo ket aotreet ar meziant", + "Authentication error" : "Fazi dilesa", + "Token expired. Please reload page." : "Jedouer re gozh. Adkargit ar bajenn.", + "PHP module %s not installed." : "Modul %s PHPn n'eo ket staliet.", + "Storage connection error. %s" : "Fazi renkañ kenstag. %s", + "Storage is temporarily not available" : "N'haller ket tizhout ar skor roadennoù evit ar poent", + "Overview" : "Taol-lagad", + "Basic settings" : "Stummoù diazez", + "Sharing" : "Rannan", + "Security" : "Surentez", + "Groupware" : "Labour a stroll", + "Personal info" : "Titouroù personel", + "Mobile & desktop" : "Hezouk ha burev" +}, +"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/br.json b/docker/overlays/nextcloud/html/lib/l10n/br.json new file mode 100644 index 0000000..b886483 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/br.json @@ -0,0 +1,53 @@ +{ "translations": { + "See %s" : "Sellet %s", + "Other activities" : "Oberiantizoù all", + "PHP %s or higher is required." : "PHP %s pe hueloc'h a zo ret kaout.", + "PHP with a version lower than %s is required." : "PHP gant ur stumm izeloc'h eget %s a zo ret kaout.", + "The library %s is not available." : "Al levraoueg %s ne c'hell ket bezhgañ implijet", + "Server version %s or higher is required." : "Stumm servijour %s pe hueloc'h rekis", + "Unknown filetype" : "N'eo ket anavezet stumm an teuliad", + "Invalid image" : "N'eo ket aotreet ar skeudenn", + "today" : "hiziv", + "yesterday" : "dec'h", + "_%n day ago_::_%n days ago_" : ["%n deiz zo","%n deiz zo","%n deiz zo","%n deiz zo","%n deiz zo"], + "last month" : "ar miz tremenet", + "_%n month ago_::_%n months ago_" : ["%n miz zo","%n miz zo","%n miz zo","%n miz zo","%n miz zo"], + "last year" : "Ar bloaz tremenet", + "_%n year ago_::_%n years ago_" : ["%n bloaz zo","%n bloaz zo","%n bloaz zo","%n bloaz zo","%n bloaz zo"], + "_%n hour ago_::_%n hours ago_" : ["%n hervez zo","%n hervez zo","%n hervez zo","%n hervez zo","%n hervez zo"], + "_%n minute ago_::_%n minutes ago_" : ["%n munudenn-zo","%n munudenn-zo","%n munudenn-zo","%n munudenn-zo","%n munudenn-zo"], + "seconds ago" : "eilenn zo", + "File name contains at least one invalid character" : "Un arouez fall ez eus d'an neubeutañ en anv restr", + "File name is too long" : "Anv ar restr a zo re hir", + "Empty filename is not allowed" : "Un anv-restr goulo n'eo ket aotreet", + "__language_name__" : "Brezhoneg", + "Help" : "Skoazell", + "Apps" : "Meziant", + "Settings" : "Arventennoù", + "Log out" : "Kuitat", + "Users" : "Implijer", + "Unknown user" : "Implijer dianv", + "Additional settings" : "Stummoù ouzhpenn", + "Set an admin username." : "Lakaat un anv-impljer merour.", + "Set an admin password." : "Lakaat ur ger-tremenn merour.", + "Sharing backend for %s not found" : "Rannadenn backend evit %s n'eo ket bet kavet", + "Open »%s«" : "Digeriñ »%s«", + "The username is already being used" : "An anv-implijet a zo dija implijet", + "User disabled" : "Implijer disaotreet", + "Login canceled by app" : "Mont tre arrestet gant ar meziant", + "Can't read file" : "N'eo ket posupl lenn ar restr", + "Application is not enabled" : "N'eo ket aotreet ar meziant", + "Authentication error" : "Fazi dilesa", + "Token expired. Please reload page." : "Jedouer re gozh. Adkargit ar bajenn.", + "PHP module %s not installed." : "Modul %s PHPn n'eo ket staliet.", + "Storage connection error. %s" : "Fazi renkañ kenstag. %s", + "Storage is temporarily not available" : "N'haller ket tizhout ar skor roadennoù evit ar poent", + "Overview" : "Taol-lagad", + "Basic settings" : "Stummoù diazez", + "Sharing" : "Rannan", + "Security" : "Surentez", + "Groupware" : "Labour a stroll", + "Personal info" : "Titouroù personel", + "Mobile & desktop" : "Hezouk ha burev" +},"pluralForm" :"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/bs.js b/docker/overlays/nextcloud/html/lib/l10n/bs.js new file mode 100644 index 0000000..8427184 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bs.js @@ -0,0 +1,58 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "Nepoznat tip datoteke", + "Invalid image" : "Nevažeća datoteka", + "__language_name__" : "Bosanski jezik", + "Help" : "Pomoć", + "Apps" : "Aplikacije", + "Settings" : "Podešavanje", + "Log out" : "Odjava", + "Users" : "Korisnici", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nije podržan i %s na ovoj platformi neće raditi kako treba. Korištenje na vlastiti rizik!", + "For the best results, please consider using a GNU/Linux server instead." : "Umjesto toga, za najbolje rezultate, molimo razmislite o mogućnosti korištenje GNU/Linux servera.", + "Sunday" : "Nedjelja", + "Monday" : "Ponedjeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Srijeda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Ut.", + "Wed." : "Sri.", + "Thu." : "Čet.", + "Fri." : "Pet.", + "Sat." : "Sub.", + "January" : "Januar", + "February" : "Februar", + "March" : "Mart", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "Avgust", + "September" : "Septembar", + "October" : "Oktobar", + "November" : "Novembar", + "December" : "Decembar", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Avg.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Nužno je navesti valjano korisničko ime", + "A valid password must be provided" : "Nužno je navesti valjanu lozinku", + "Authentication error" : "Grešna autentifikacije", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Uzrok tome je vjerojatno neki ubrzivač predmemorisanja kao što je Zend OPcache ili eAccelerator.", + "Sharing" : "Dijeljenje" +}, +"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/bs.json b/docker/overlays/nextcloud/html/lib/l10n/bs.json new file mode 100644 index 0000000..8873de7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/bs.json @@ -0,0 +1,56 @@ +{ "translations": { + "Unknown filetype" : "Nepoznat tip datoteke", + "Invalid image" : "Nevažeća datoteka", + "__language_name__" : "Bosanski jezik", + "Help" : "Pomoć", + "Apps" : "Aplikacije", + "Settings" : "Podešavanje", + "Log out" : "Odjava", + "Users" : "Korisnici", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nije podržan i %s na ovoj platformi neće raditi kako treba. Korištenje na vlastiti rizik!", + "For the best results, please consider using a GNU/Linux server instead." : "Umjesto toga, za najbolje rezultate, molimo razmislite o mogućnosti korištenje GNU/Linux servera.", + "Sunday" : "Nedjelja", + "Monday" : "Ponedjeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Srijeda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Ut.", + "Wed." : "Sri.", + "Thu." : "Čet.", + "Fri." : "Pet.", + "Sat." : "Sub.", + "January" : "Januar", + "February" : "Februar", + "March" : "Mart", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "Avgust", + "September" : "Septembar", + "October" : "Oktobar", + "November" : "Novembar", + "December" : "Decembar", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Avg.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Nužno je navesti valjano korisničko ime", + "A valid password must be provided" : "Nužno je navesti valjanu lozinku", + "Authentication error" : "Grešna autentifikacije", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Uzrok tome je vjerojatno neki ubrzivač predmemorisanja kao što je Zend OPcache ili eAccelerator.", + "Sharing" : "Dijeljenje" +},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ca.js b/docker/overlays/nextcloud/html/lib/l10n/ca.js new file mode 100644 index 0000000..b9a3423 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ca.js @@ -0,0 +1,239 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "No es pot escriure al directori \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori de configuració", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, si preferiu mantenir el fitxer config.php només de lectura, establiu-hi l’opció \"config_is_read_only\" com a certa (true).", + "See %s" : "Vegeu %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Això normalment es pot arreglar donant al servidor web accés d'escriptura al directori de configuració.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si preferiu mantenir el fitxer config.php només de lectura, establiu-hi l’opció \"config_is_read_only\" com a certa (true). Vegeu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Els fitxers de l’aplicació %1$s no s’han substituït correctament. Assegureu-vos que és una versió compatible amb el servidor.", + "Sample configuration detected" : "S'ha detectat una configuració d'exemple", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S'ha detectat que la configuració d'exemple ha estat copiada. Això no està suportat, i podria corrompre la vostra instalació. Si us plau, llegiu la documentació abans de fer cap canvi a config.php", + "Other activities" : "Altres activitats", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Edició educativa", + "Enterprise bundle" : "Paquet empresarial", + "Groupware bundle" : "Paquet de treball en grup", + "Hub bundle" : "Paquet del centre", + "Social sharing bundle" : "Paquet social", + "PHP %s or higher is required." : "Es requereix PHP %s o superior.", + "PHP with a version lower than %s is required." : "Es requereix PHP amb versió inferior a %s.", + "%sbit or higher PHP required." : "Es requereix PHP de %s bits o superior.", + "The following databases are supported: %s" : "S'admeten les bases de dades següents: %s", + "The command line tool %s could not be found" : "No s’ha trobat l’eina de línia d’ordres %s", + "The library %s is not available." : "La llibreria %s no està disponible.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Es requereix la llibreria %1$s amb una versió superior a %2$s - la versió disponible és %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Es requereix la llibreria %1$s amb una versió inferior a %2$s - la versió disponible és %3$s.", + "The following platforms are supported: %s" : "S'admeten les següents plataformes: %s", + "Server version %s or higher is required." : "Es requereix una versió de servidor %s o superior.", + "Server version %s or lower is required." : "Es requereix una versió de servidor %s o inferior.", + "Logged in user must be an admin or sub admin" : "L'usuari que ha iniciat la sessió ha de ser un administrador o un subadministrador", + "Logged in user must be an admin" : "L'usuari que ha iniciat la sessió ha de ser un administrador", + "Wiping of device %s has started" : "Ha començat la neteja del dispositiu %s", + "Wiping of device »%s« has started" : "Ha començat la neteja del dispositiu »%s«", + "»%s« started remote wipe" : "»%s« ha començat la neteja remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "El dispositiu o aplicació »%s« ha començat el procés de neteja remota, Rebreu un altre correu un cop que el procés finalitzi", + "Wiping of device %s has finished" : "S'ha enllestit la neteja del dispositiu %s", + "Wiping of device »%s« has finished" : "S'ha enllestit la neteja del dispositiu »%s«", + "»%s« finished remote wipe" : "S'ha enllestit la neteja remota de »%s«", + "Device or application »%s« has finished the remote wipe process." : "El dispositiu o aplicació »%s« ha enllestit el procés de neteja remota.", + "Remote wipe started" : "S'ha iniciat la neteja remota", + "A remote wipe was started on device %s" : "S'ha engegat una neteja remota en el dispositiu %s", + "Remote wipe finished" : "Ha finalitzat la neteja remota", + "The remote wipe on %s has finished" : "Ha finalitzat la neteja remota a %s", + "Authentication" : "Autenticació", + "Unknown filetype" : "Tipus de fitxer desconegut", + "Invalid image" : "Imatge no vàlida", + "Avatar image is not square" : "La imatge de perfil no és quadrada", + "today" : "avui", + "tomorrow" : "demà", + "yesterday" : "ahir", + "_in %n day_::_in %n days_" : ["d'aquí a %n dia","d'aquí a %n dies"], + "_%n day ago_::_%n days ago_" : ["fa %n dia","fa %n dies"], + "next month" : "mes següent", + "last month" : "el mes passat", + "_in %n month_::_in %n months_" : ["d'aquí a %n mes","d'aquí a %n mesos"], + "_%n month ago_::_%n months ago_" : ["fa %n mes","fa %n mesos"], + "next year" : "any següent", + "last year" : "l'any passat", + "_in %n year_::_in %n years_" : ["d'aquí a %n any","d'aquí a %n anys"], + "_%n year ago_::_%n years ago_" : ["fa %n any","fa %n anys"], + "_in %n hour_::_in %n hours_" : ["d'aquí a %n hora","d'aquí a %n hores"], + "_%n hour ago_::_%n hours ago_" : ["fa %n hora","fa %n hores"], + "_in %n minute_::_in %n minutes_" : ["d'aquí a %n minut","d'aquí a %n minuts"], + "_%n minute ago_::_%n minutes ago_" : ["fa %n minut","fa %n minuts"], + "in a few seconds" : "d'aquí uns segons", + "seconds ago" : "fa uns segons", + "Empty file" : "Fitxer buit", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Mòdul amb l'ID: %s no existeix. Si us plau, activeu-lo a la configuració de les aplicacions o poseu-vos en contacte amb el vostre administrador.", + "File name is a reserved word" : "El nom de fitxer és una paraula reservada", + "File name contains at least one invalid character" : "El nom del fitxer conté almenys un caràcter no vàlid", + "File name is too long" : "El nom del fitxer és massa gran", + "Dot files are not allowed" : "No estan permesos els fitxers que comencin amb un punt", + "Empty filename is not allowed" : "No estan permesos els noms de fitxers buits", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicació \"%s\" no es pot instal·lar perquè el fitxer appinfo no es pot llegir.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'aplicació \"%s\" no es pot instal·lar perquè no és compatible amb aquesta versió del servidor.", + "__language_name__" : "Català", + "This is an automatically sent email, please do not reply." : "Aquest és un correu electrònic enviat automàticament, si us plau no el respongueu.", + "Help" : "Ajuda", + "Apps" : "Aplicacions", + "Settings" : "Paràmetres", + "Log out" : "Tanca la sessió", + "Users" : "Usuaris", + "Unknown user" : "Usuari desconegut", + "Additional settings" : "Paràmetres addicionals", + "%s enter the database username and name." : "%s escriviu el nom d'usuari i el nom de la base de dades.", + "%s enter the database username." : "%s escriviu el nom d'usuari de la base de dades.", + "%s enter the database name." : "%s escriviu el nom de la base de dades.", + "%s you may not use dots in the database name" : "%s no podeu fer servir punts en el nom de la base de dades", + "MySQL username and/or password not valid" : "El nom d'usuari i/o contrasenya de MySQL no són vàlids", + "You need to enter details of an existing account." : "Heu d’introduir els detalls d’un compte existent.", + "Oracle connection could not be established" : "No s'ha pogut establir la connexió Oracle", + "Oracle username and/or password not valid" : "Nom d'usuari i/o contrasenya d'Oracle no vàlids", + "PostgreSQL username and/or password not valid" : "Nom d'usuari i/o contrasenya de PostgreSQL no vàlids", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X no té suport i %s no funcionarà correctament en aquesta plataforma. Feu-lo servir al vostre propi risc! ", + "For the best results, please consider using a GNU/Linux server instead." : "Per obtenir els millors resultats, si us plau plantegeu-vos fer servir un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Sembla que aquesta instància %s s'està executant en un entorn PHP de 32 bits i l'open_basedir s'ha configurat a php.ini. Això comportarà problemes amb fitxers de més de 4 GB i està molt poc recomanat.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Si us plau, suprimiu l’opció open_basedir del vostre php.ini o canvieu a PHP de 64 bits.", + "Set an admin username." : "Establiu un nom d'usuari per l'administrador.", + "Set an admin password." : "Establiu una contrasenya per l'administrador.", + "Can't create or write into the data directory %s" : "No es pot crear o escriure al directori de dades %s", + "Invalid Federated Cloud ID" : "ID de Núvol Federat no vàlid", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El rerefons de compartició %s ha d'implementar la interfície OCP\\Share_Backend", + "Sharing backend %s not found" : "El rerefons de compartició %s no s'ha trobat", + "Sharing backend for %s not found" : "El rerefons de compartició per a %s no s'ha trobat", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s us ha compartit »%2$s« i vol afegir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s us ha compartit »%2$s« i vol afegir", + "»%s« added a note to a file shared with you" : "»%s« ha afegit una anotació a un fitxer amb qui teniu compartit", + "Open »%s«" : "Obre »%s«", + "%1$s via %2$s" : "%1$s mitjançant %2$s", + "You are not allowed to share %s" : "No se us permet compartir %s", + "Can’t increase permissions of %s" : "No es poden augmentar els permisos de %s", + "Files can’t be shared with delete permissions" : "No es poden compartir els fitxers amb permisos de supressió", + "Files can’t be shared with create permissions" : "No es poden compartir els fitxers amb permisos de creació", + "Expiration date is in the past" : "La data de caducitat és del passat", + "Can’t set expiration date more than %s days in the future" : "No es pot establir la data de caducitat més de %s dies en el futur", + "%1$s shared »%2$s« with you" : "%1$s us ha compartit »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s us ha compartit »%2$s«.", + "Click the button below to open it." : "Feu clic al botó de sota per obrir-lo.", + "The requested share does not exist anymore" : "La compartició sol·licitada ja no existeix", + "Could not find category \"%s\"" : "No s'ha trobat la categoria \"%s\"", + "Sunday" : "Diumenge", + "Monday" : "Dilluns", + "Tuesday" : "Dimarts", + "Wednesday" : "Dimecres", + "Thursday" : "Dijous", + "Friday" : "Divendres", + "Saturday" : "Dissabte", + "Sun." : "Dg.", + "Mon." : "Dl.", + "Tue." : "Dt.", + "Wed." : "Dc.", + "Thu." : "Dj.", + "Fri." : "Dv.", + "Sat." : "Ds.", + "Su" : "Dg", + "Mo" : "Dl", + "Tu" : "Dt", + "We" : "Dc", + "Th" : "Dj", + "Fr" : "Dv", + "Sa" : "Ds", + "January" : "Gener", + "February" : "Febrer", + "March" : "Març", + "April" : "Abril", + "May" : "Maig", + "June" : "Juny", + "July" : "Juliol", + "August" : "Agost", + "September" : "Setembre", + "October" : "Octubre", + "November" : "Novembre", + "December" : "Desembre", + "Jan." : "Gen.", + "Feb." : "Febr.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ag.", + "Sep." : "Set.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Només es permeten els següents caràcters en un nom d’usuari: \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"", + "A valid username must be provided" : "Heu de facilitar un nom d'usuari vàlid", + "Username contains whitespace at the beginning or at the end" : "El nom d’usuari conté espais en blanc al principi o al final", + "Username must not consist of dots only" : "El nom d'usuari no pot està format només per punts", + "Username is invalid because files already exist for this user" : "El nom d'usuari no és vàlid perquè els fitxers ja existeixen per a aquest usuari", + "A valid password must be provided" : "Heu de facilitar una contrasenya vàlida", + "The username is already being used" : "El nom d'usuari ja està en ús", + "Could not create user" : "No s'ha pogut crear l'usuari", + "User disabled" : "Usuari desactivat", + "Login canceled by app" : "Accés cancel·lat per l'App", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'aplicació \"%1$s\" no es pot instal·lar perquè no es compleixen les dependències següents: %2$s", + "a safe home for all your data" : "un lloc segur per a les vostres dades", + "File is currently busy, please try again later" : "El fitxer està ocupat actualment, si us plau torneu-ho a provar més tard", + "Can't read file" : "No es pot llegir el fitxer", + "Application is not enabled" : "L'aplicació no està activada", + "Authentication error" : "Error d'autenticació", + "Token expired. Please reload page." : "El testimoni ha expirat. Torneu a carregar la pàgina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No hi ha instal·lats controladors de bases de dades (sqlite, mysql o postgresql).", + "Cannot write into \"config\" directory" : "No es pot escriure al directori \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori de configuració. Vegeu %s", + "Cannot write into \"apps\" directory" : "No es pot escriure al directori \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Això normalment es pot arreglar donant al servidor web escriure accés al directori d'aplicacions o desactivar la botiga d’apps al fitxer de configuració.", + "Cannot create \"data\" directory" : "No es pot crear el directori \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori arrel. Vegeu %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Els permisos generalment es poden solucionar donant al servidor web accés d’escriptura al directori arrel. Vegeu %s.", + "Setting locale to %s failed" : "Ha fallat l'establiment a l'idioma %s", + "Please install one of these locales on your system and restart your webserver." : "Si us plau, instal·leu un d'aquests fitxers de localització en el vostre sistema, i reinicieu el vostre servidor web.", + "PHP module %s not installed." : "El mòdul PHP %s no està instal·lat.", + "Please ask your server administrator to install the module." : "Si us plau, demaneu a l'administrador del sistema que instal·li el mòdul.", + "PHP setting \"%s\" is not set to \"%s\"." : "El paràmetre de PHP \"%s\" no està configurat a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustant aquest paràmetre a php.ini, tornarà a funcionar Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload està configurat a \"%s\" en comptes del valor esperat \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Per solucionar aquest problema configureu mbstring.func_overload a 0 en el vostre php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "es requereix com a mínim libxml2 2.7.0. Actualment hi ha instal·lat %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Per solucionar aquest problema actualitzeu la vostra versió de libxml2 i reinicieu el servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Sembla que PHP està configurat per suprimir els blocs de documents en línia. Això farà que diverses aplicacions principals no siguin accessibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Això probablement està provocat per un mecanisme de memòria cau/accelerador com Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "S'han instal·lat mòduls PHP, però encara es llisten com una mancança?", + "Please ask your server administrator to restart the web server." : "Si us plau, demaneu a l'administrador que reiniciï el servidor web.", + "PostgreSQL >= 9 required" : "Es requereix PostgreSQL >= 9", + "Please upgrade your database version" : "Si us plau, actualitzeu la versió de la vostra base de dades", + "Your data directory is readable by other users" : "El vostre directori de dades és llegible per altres usuaris", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Si us plau, canvieu els permisos a 0770 per tal que el directori no pugui ser llistat per altres usuaris.", + "Your data directory must be an absolute path" : "El vostre directori de dades ha de ser un camí absolut", + "Check the value of \"datadirectory\" in your configuration" : "Comproveu el valor de \"datadirectory\" a la vostra configuració", + "Your data directory is invalid" : "El vostre directori de dades no és vàlid", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assegureu-vos que hi ha un fitxer anomenat \".ocdata\" a l’arrel del directori de dades.", + "Action \"%s\" not supported or implemented." : "L'acció \"%s\" no està suportada o implementada.", + "Authentication failed, wrong token or provider ID given" : "Ha fallat l’autenticació, s’ha donat un identificador de proveïdor o un testimoni incorrecte", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Falten paràmetres per completar la sol·licitud. Els paràmetres que falten són: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'ID \"%1$s\" ja es fa servir pel proveïdor de la federació del núvol \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "El Proveïdor de la Federació de Núvol amb ID: \"%s\" no existeix.", + "Could not obtain lock type %d on \"%s\"." : "No s'ha pogut obtenir un bloqueig tipus %d a \"%s\".", + "Storage unauthorized. %s" : "Emmagatzematge no autoritzat. %s", + "Storage incomplete configuration. %s" : "Configuració d'emmagatzematge incompleta. %s", + "Storage connection error. %s" : "Error de connexió d’emmagatzematge. %s", + "Storage is temporarily not available" : "Emmagatzematge temporalment no disponible", + "Storage connection timeout. %s" : "Temps d’espera exhaurit en la connexió d’emmagatzematge. %s", + "Following databases are supported: %s" : "S'admeten les següents bases de dades: %s", + "Following platforms are supported: %s" : "S'admeten les següents plataformes: %s", + "Overview" : "Resum", + "Basic settings" : "Configuració bàsica", + "Sharing" : "Compartició", + "Security" : "Seguretat", + "Groupware" : "Treball en grup", + "Personal info" : "Informació personal", + "Mobile & desktop" : "Mòbil i escriptori", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori d'aplicacions o desactivant el programa d’aplicació al fitxer de configuració. Vegeu %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ca.json b/docker/overlays/nextcloud/html/lib/l10n/ca.json new file mode 100644 index 0000000..1af4705 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ca.json @@ -0,0 +1,237 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "No es pot escriure al directori \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori de configuració", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, si preferiu mantenir el fitxer config.php només de lectura, establiu-hi l’opció \"config_is_read_only\" com a certa (true).", + "See %s" : "Vegeu %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Això normalment es pot arreglar donant al servidor web accés d'escriptura al directori de configuració.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si preferiu mantenir el fitxer config.php només de lectura, establiu-hi l’opció \"config_is_read_only\" com a certa (true). Vegeu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Els fitxers de l’aplicació %1$s no s’han substituït correctament. Assegureu-vos que és una versió compatible amb el servidor.", + "Sample configuration detected" : "S'ha detectat una configuració d'exemple", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S'ha detectat que la configuració d'exemple ha estat copiada. Això no està suportat, i podria corrompre la vostra instalació. Si us plau, llegiu la documentació abans de fer cap canvi a config.php", + "Other activities" : "Altres activitats", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Edició educativa", + "Enterprise bundle" : "Paquet empresarial", + "Groupware bundle" : "Paquet de treball en grup", + "Hub bundle" : "Paquet del centre", + "Social sharing bundle" : "Paquet social", + "PHP %s or higher is required." : "Es requereix PHP %s o superior.", + "PHP with a version lower than %s is required." : "Es requereix PHP amb versió inferior a %s.", + "%sbit or higher PHP required." : "Es requereix PHP de %s bits o superior.", + "The following databases are supported: %s" : "S'admeten les bases de dades següents: %s", + "The command line tool %s could not be found" : "No s’ha trobat l’eina de línia d’ordres %s", + "The library %s is not available." : "La llibreria %s no està disponible.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Es requereix la llibreria %1$s amb una versió superior a %2$s - la versió disponible és %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Es requereix la llibreria %1$s amb una versió inferior a %2$s - la versió disponible és %3$s.", + "The following platforms are supported: %s" : "S'admeten les següents plataformes: %s", + "Server version %s or higher is required." : "Es requereix una versió de servidor %s o superior.", + "Server version %s or lower is required." : "Es requereix una versió de servidor %s o inferior.", + "Logged in user must be an admin or sub admin" : "L'usuari que ha iniciat la sessió ha de ser un administrador o un subadministrador", + "Logged in user must be an admin" : "L'usuari que ha iniciat la sessió ha de ser un administrador", + "Wiping of device %s has started" : "Ha començat la neteja del dispositiu %s", + "Wiping of device »%s« has started" : "Ha començat la neteja del dispositiu »%s«", + "»%s« started remote wipe" : "»%s« ha començat la neteja remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "El dispositiu o aplicació »%s« ha començat el procés de neteja remota, Rebreu un altre correu un cop que el procés finalitzi", + "Wiping of device %s has finished" : "S'ha enllestit la neteja del dispositiu %s", + "Wiping of device »%s« has finished" : "S'ha enllestit la neteja del dispositiu »%s«", + "»%s« finished remote wipe" : "S'ha enllestit la neteja remota de »%s«", + "Device or application »%s« has finished the remote wipe process." : "El dispositiu o aplicació »%s« ha enllestit el procés de neteja remota.", + "Remote wipe started" : "S'ha iniciat la neteja remota", + "A remote wipe was started on device %s" : "S'ha engegat una neteja remota en el dispositiu %s", + "Remote wipe finished" : "Ha finalitzat la neteja remota", + "The remote wipe on %s has finished" : "Ha finalitzat la neteja remota a %s", + "Authentication" : "Autenticació", + "Unknown filetype" : "Tipus de fitxer desconegut", + "Invalid image" : "Imatge no vàlida", + "Avatar image is not square" : "La imatge de perfil no és quadrada", + "today" : "avui", + "tomorrow" : "demà", + "yesterday" : "ahir", + "_in %n day_::_in %n days_" : ["d'aquí a %n dia","d'aquí a %n dies"], + "_%n day ago_::_%n days ago_" : ["fa %n dia","fa %n dies"], + "next month" : "mes següent", + "last month" : "el mes passat", + "_in %n month_::_in %n months_" : ["d'aquí a %n mes","d'aquí a %n mesos"], + "_%n month ago_::_%n months ago_" : ["fa %n mes","fa %n mesos"], + "next year" : "any següent", + "last year" : "l'any passat", + "_in %n year_::_in %n years_" : ["d'aquí a %n any","d'aquí a %n anys"], + "_%n year ago_::_%n years ago_" : ["fa %n any","fa %n anys"], + "_in %n hour_::_in %n hours_" : ["d'aquí a %n hora","d'aquí a %n hores"], + "_%n hour ago_::_%n hours ago_" : ["fa %n hora","fa %n hores"], + "_in %n minute_::_in %n minutes_" : ["d'aquí a %n minut","d'aquí a %n minuts"], + "_%n minute ago_::_%n minutes ago_" : ["fa %n minut","fa %n minuts"], + "in a few seconds" : "d'aquí uns segons", + "seconds ago" : "fa uns segons", + "Empty file" : "Fitxer buit", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Mòdul amb l'ID: %s no existeix. Si us plau, activeu-lo a la configuració de les aplicacions o poseu-vos en contacte amb el vostre administrador.", + "File name is a reserved word" : "El nom de fitxer és una paraula reservada", + "File name contains at least one invalid character" : "El nom del fitxer conté almenys un caràcter no vàlid", + "File name is too long" : "El nom del fitxer és massa gran", + "Dot files are not allowed" : "No estan permesos els fitxers que comencin amb un punt", + "Empty filename is not allowed" : "No estan permesos els noms de fitxers buits", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicació \"%s\" no es pot instal·lar perquè el fitxer appinfo no es pot llegir.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'aplicació \"%s\" no es pot instal·lar perquè no és compatible amb aquesta versió del servidor.", + "__language_name__" : "Català", + "This is an automatically sent email, please do not reply." : "Aquest és un correu electrònic enviat automàticament, si us plau no el respongueu.", + "Help" : "Ajuda", + "Apps" : "Aplicacions", + "Settings" : "Paràmetres", + "Log out" : "Tanca la sessió", + "Users" : "Usuaris", + "Unknown user" : "Usuari desconegut", + "Additional settings" : "Paràmetres addicionals", + "%s enter the database username and name." : "%s escriviu el nom d'usuari i el nom de la base de dades.", + "%s enter the database username." : "%s escriviu el nom d'usuari de la base de dades.", + "%s enter the database name." : "%s escriviu el nom de la base de dades.", + "%s you may not use dots in the database name" : "%s no podeu fer servir punts en el nom de la base de dades", + "MySQL username and/or password not valid" : "El nom d'usuari i/o contrasenya de MySQL no són vàlids", + "You need to enter details of an existing account." : "Heu d’introduir els detalls d’un compte existent.", + "Oracle connection could not be established" : "No s'ha pogut establir la connexió Oracle", + "Oracle username and/or password not valid" : "Nom d'usuari i/o contrasenya d'Oracle no vàlids", + "PostgreSQL username and/or password not valid" : "Nom d'usuari i/o contrasenya de PostgreSQL no vàlids", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X no té suport i %s no funcionarà correctament en aquesta plataforma. Feu-lo servir al vostre propi risc! ", + "For the best results, please consider using a GNU/Linux server instead." : "Per obtenir els millors resultats, si us plau plantegeu-vos fer servir un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Sembla que aquesta instància %s s'està executant en un entorn PHP de 32 bits i l'open_basedir s'ha configurat a php.ini. Això comportarà problemes amb fitxers de més de 4 GB i està molt poc recomanat.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Si us plau, suprimiu l’opció open_basedir del vostre php.ini o canvieu a PHP de 64 bits.", + "Set an admin username." : "Establiu un nom d'usuari per l'administrador.", + "Set an admin password." : "Establiu una contrasenya per l'administrador.", + "Can't create or write into the data directory %s" : "No es pot crear o escriure al directori de dades %s", + "Invalid Federated Cloud ID" : "ID de Núvol Federat no vàlid", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El rerefons de compartició %s ha d'implementar la interfície OCP\\Share_Backend", + "Sharing backend %s not found" : "El rerefons de compartició %s no s'ha trobat", + "Sharing backend for %s not found" : "El rerefons de compartició per a %s no s'ha trobat", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s us ha compartit »%2$s« i vol afegir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s us ha compartit »%2$s« i vol afegir", + "»%s« added a note to a file shared with you" : "»%s« ha afegit una anotació a un fitxer amb qui teniu compartit", + "Open »%s«" : "Obre »%s«", + "%1$s via %2$s" : "%1$s mitjançant %2$s", + "You are not allowed to share %s" : "No se us permet compartir %s", + "Can’t increase permissions of %s" : "No es poden augmentar els permisos de %s", + "Files can’t be shared with delete permissions" : "No es poden compartir els fitxers amb permisos de supressió", + "Files can’t be shared with create permissions" : "No es poden compartir els fitxers amb permisos de creació", + "Expiration date is in the past" : "La data de caducitat és del passat", + "Can’t set expiration date more than %s days in the future" : "No es pot establir la data de caducitat més de %s dies en el futur", + "%1$s shared »%2$s« with you" : "%1$s us ha compartit »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s us ha compartit »%2$s«.", + "Click the button below to open it." : "Feu clic al botó de sota per obrir-lo.", + "The requested share does not exist anymore" : "La compartició sol·licitada ja no existeix", + "Could not find category \"%s\"" : "No s'ha trobat la categoria \"%s\"", + "Sunday" : "Diumenge", + "Monday" : "Dilluns", + "Tuesday" : "Dimarts", + "Wednesday" : "Dimecres", + "Thursday" : "Dijous", + "Friday" : "Divendres", + "Saturday" : "Dissabte", + "Sun." : "Dg.", + "Mon." : "Dl.", + "Tue." : "Dt.", + "Wed." : "Dc.", + "Thu." : "Dj.", + "Fri." : "Dv.", + "Sat." : "Ds.", + "Su" : "Dg", + "Mo" : "Dl", + "Tu" : "Dt", + "We" : "Dc", + "Th" : "Dj", + "Fr" : "Dv", + "Sa" : "Ds", + "January" : "Gener", + "February" : "Febrer", + "March" : "Març", + "April" : "Abril", + "May" : "Maig", + "June" : "Juny", + "July" : "Juliol", + "August" : "Agost", + "September" : "Setembre", + "October" : "Octubre", + "November" : "Novembre", + "December" : "Desembre", + "Jan." : "Gen.", + "Feb." : "Febr.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ag.", + "Sep." : "Set.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Només es permeten els següents caràcters en un nom d’usuari: \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"", + "A valid username must be provided" : "Heu de facilitar un nom d'usuari vàlid", + "Username contains whitespace at the beginning or at the end" : "El nom d’usuari conté espais en blanc al principi o al final", + "Username must not consist of dots only" : "El nom d'usuari no pot està format només per punts", + "Username is invalid because files already exist for this user" : "El nom d'usuari no és vàlid perquè els fitxers ja existeixen per a aquest usuari", + "A valid password must be provided" : "Heu de facilitar una contrasenya vàlida", + "The username is already being used" : "El nom d'usuari ja està en ús", + "Could not create user" : "No s'ha pogut crear l'usuari", + "User disabled" : "Usuari desactivat", + "Login canceled by app" : "Accés cancel·lat per l'App", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'aplicació \"%1$s\" no es pot instal·lar perquè no es compleixen les dependències següents: %2$s", + "a safe home for all your data" : "un lloc segur per a les vostres dades", + "File is currently busy, please try again later" : "El fitxer està ocupat actualment, si us plau torneu-ho a provar més tard", + "Can't read file" : "No es pot llegir el fitxer", + "Application is not enabled" : "L'aplicació no està activada", + "Authentication error" : "Error d'autenticació", + "Token expired. Please reload page." : "El testimoni ha expirat. Torneu a carregar la pàgina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No hi ha instal·lats controladors de bases de dades (sqlite, mysql o postgresql).", + "Cannot write into \"config\" directory" : "No es pot escriure al directori \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori de configuració. Vegeu %s", + "Cannot write into \"apps\" directory" : "No es pot escriure al directori \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Això normalment es pot arreglar donant al servidor web escriure accés al directori d'aplicacions o desactivar la botiga d’apps al fitxer de configuració.", + "Cannot create \"data\" directory" : "No es pot crear el directori \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori arrel. Vegeu %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Els permisos generalment es poden solucionar donant al servidor web accés d’escriptura al directori arrel. Vegeu %s.", + "Setting locale to %s failed" : "Ha fallat l'establiment a l'idioma %s", + "Please install one of these locales on your system and restart your webserver." : "Si us plau, instal·leu un d'aquests fitxers de localització en el vostre sistema, i reinicieu el vostre servidor web.", + "PHP module %s not installed." : "El mòdul PHP %s no està instal·lat.", + "Please ask your server administrator to install the module." : "Si us plau, demaneu a l'administrador del sistema que instal·li el mòdul.", + "PHP setting \"%s\" is not set to \"%s\"." : "El paràmetre de PHP \"%s\" no està configurat a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustant aquest paràmetre a php.ini, tornarà a funcionar Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload està configurat a \"%s\" en comptes del valor esperat \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Per solucionar aquest problema configureu mbstring.func_overload a 0 en el vostre php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "es requereix com a mínim libxml2 2.7.0. Actualment hi ha instal·lat %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Per solucionar aquest problema actualitzeu la vostra versió de libxml2 i reinicieu el servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Sembla que PHP està configurat per suprimir els blocs de documents en línia. Això farà que diverses aplicacions principals no siguin accessibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Això probablement està provocat per un mecanisme de memòria cau/accelerador com Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "S'han instal·lat mòduls PHP, però encara es llisten com una mancança?", + "Please ask your server administrator to restart the web server." : "Si us plau, demaneu a l'administrador que reiniciï el servidor web.", + "PostgreSQL >= 9 required" : "Es requereix PostgreSQL >= 9", + "Please upgrade your database version" : "Si us plau, actualitzeu la versió de la vostra base de dades", + "Your data directory is readable by other users" : "El vostre directori de dades és llegible per altres usuaris", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Si us plau, canvieu els permisos a 0770 per tal que el directori no pugui ser llistat per altres usuaris.", + "Your data directory must be an absolute path" : "El vostre directori de dades ha de ser un camí absolut", + "Check the value of \"datadirectory\" in your configuration" : "Comproveu el valor de \"datadirectory\" a la vostra configuració", + "Your data directory is invalid" : "El vostre directori de dades no és vàlid", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assegureu-vos que hi ha un fitxer anomenat \".ocdata\" a l’arrel del directori de dades.", + "Action \"%s\" not supported or implemented." : "L'acció \"%s\" no està suportada o implementada.", + "Authentication failed, wrong token or provider ID given" : "Ha fallat l’autenticació, s’ha donat un identificador de proveïdor o un testimoni incorrecte", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Falten paràmetres per completar la sol·licitud. Els paràmetres que falten són: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'ID \"%1$s\" ja es fa servir pel proveïdor de la federació del núvol \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "El Proveïdor de la Federació de Núvol amb ID: \"%s\" no existeix.", + "Could not obtain lock type %d on \"%s\"." : "No s'ha pogut obtenir un bloqueig tipus %d a \"%s\".", + "Storage unauthorized. %s" : "Emmagatzematge no autoritzat. %s", + "Storage incomplete configuration. %s" : "Configuració d'emmagatzematge incompleta. %s", + "Storage connection error. %s" : "Error de connexió d’emmagatzematge. %s", + "Storage is temporarily not available" : "Emmagatzematge temporalment no disponible", + "Storage connection timeout. %s" : "Temps d’espera exhaurit en la connexió d’emmagatzematge. %s", + "Following databases are supported: %s" : "S'admeten les següents bases de dades: %s", + "Following platforms are supported: %s" : "S'admeten les següents plataformes: %s", + "Overview" : "Resum", + "Basic settings" : "Configuració bàsica", + "Sharing" : "Compartició", + "Security" : "Seguretat", + "Groupware" : "Treball en grup", + "Personal info" : "Informació personal", + "Mobile & desktop" : "Mòbil i escriptori", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Això normalment es pot solucionar donant al servidor web permís d'escriptura al directori d'aplicacions o desactivant el programa d’aplicació al fitxer de configuració. Vegeu %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/cs.js b/docker/overlays/nextcloud/html/lib/l10n/cs.js new file mode 100644 index 0000000..6ee405b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/cs.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nedaří se zapisovat do adresáře „config“!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do adresáře s nastaveními pro účet, pod kterým je provozován webový server", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Nebo, pokud chcete mít soubor config.php pouze pro čtení, nastavte v něm volbu „config_is_read_only“ na hodnotu true.", + "See %s" : "Viz %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Toto je obvykle možné vyřešit udělením webovému serveru oprávnění k zápisu do adresáře s nastaveními.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Nebo, pokud chcete mít soubor config.php pouze pro čtení, nastavte v něm volbu „config_is_read_only“ na hodnotu true. Viz %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.", + "Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci", + "Other activities" : "Ostatní aktivity", + "%1$s and %2$s" : "%1$s a %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s a %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s a %5$s", + "Education Edition" : "Vydání pro vzdělávací instituce", + "Enterprise bundle" : "Sada pro organizace", + "Groupware bundle" : "Sada pro podporu spolupráce", + "Hub bundle" : "Sada pro centrum aktivity (hub)", + "Social sharing bundle" : "Balíček pro sdílení na sociálních sítích", + "PHP %s or higher is required." : "Je vyžadováno PHP %s nebo novější.", + "PHP with a version lower than %s is required." : "Je vyžadováno PHP ve verzi nižší než %s.", + "%sbit or higher PHP required." : "Je vyžadováno PHP %sbit nebo vyšší.", + "The following architectures are supported: %s" : "Jsou podporovány následující architektury: %s", + "The following databases are supported: %s" : "Jsou podporovány následující databáze: %s", + "The command line tool %s could not be found" : "Nástroj příkazového řádku %s nebyl nalezen", + "The library %s is not available." : "Knihovna %s není dostupná.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Je vyžadována knihovna %1$s novější verze než %2$s – verze k dispozici je %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Je vyžadována knihovna %1$s verzi nižší než %2$s – dostupná verze %3$s.", + "The following platforms are supported: %s" : "Jsou podporovány následující systémy: %s", + "Server version %s or higher is required." : "Je potřeba verze serveru %s nebo novější.", + "Server version %s or lower is required." : "Je potřeba verze serveru %s nebo starší.", + "Logged in user must be an admin or sub admin" : "Je třeba, aby přihlášený uživatel byl správcem či správcem pro dílčí oblast", + "Logged in user must be an admin" : "Je třeba, aby přihlášený uživatel byl správce", + "Wiping of device %s has started" : "Vymazávání zařízení %s zahájeno", + "Wiping of device »%s« has started" : "Vymazávání zařízení „%s“ zahájeno", + "»%s« started remote wipe" : "„%s“ zahájilo vymazávání na dálku", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Přístroj či aplikace »%s« spustila proces vzdáleného vymazání. Obdržíte další e-mail poté co bude proces ukončen", + "Wiping of device %s has finished" : "Vymazávání zařízení %s dokončeno", + "Wiping of device »%s« has finished" : "Vymazávání zařízení „%s“ dokončeno", + "»%s« finished remote wipe" : "„%s“ dokončilo vymazání na dálku", + "Device or application »%s« has finished the remote wipe process." : "Přístroj či aplikace „%s“ dokončila proces vymazání na dálku.", + "Remote wipe started" : "Vymazání na dálku zahájeno", + "A remote wipe was started on device %s" : "Na zařízení %s bylo spuštěno vymazání na dálku", + "Remote wipe finished" : "Vymazání na dálku dokončeno", + "The remote wipe on %s has finished" : "Vymazání %s na dálku dokončeno", + "Authentication" : "Ověření", + "Unknown filetype" : "Neznámý typ souboru", + "Invalid image" : "Neplatný obrázek", + "Avatar image is not square" : "Obrázek s avatarem není čtvercový", + "today" : "dnes", + "tomorrow" : "zítra", + "yesterday" : "včera", + "_in %n day_::_in %n days_" : ["během %n dne","během %n dnů","během %n dnů","během %n dnů"], + "_%n day ago_::_%n days ago_" : ["včera","před %n dny","před %n dny","před %n dny"], + "next month" : "následující měsíc", + "last month" : "minulý měsíc", + "_in %n month_::_in %n months_" : ["během %n měsíce","během %n měsíců","během %n měsíců","během %n měsíců"], + "_%n month ago_::_%n months ago_" : ["před %n měsícem","před %n měsíci","před %n měsíci","před %n měsíci"], + "next year" : "následující rok", + "last year" : "minulý rok", + "_in %n year_::_in %n years_" : ["během %n roku","během %n roků","během %n roků","během %n roků"], + "_%n year ago_::_%n years ago_" : ["před rokem","před %n lety","před %n lety","před %n lety"], + "_in %n hour_::_in %n hours_" : ["během %n hodiny","během %n hodin","během %n hodin","během %n hodin"], + "_%n hour ago_::_%n hours ago_" : ["před %n hodinou","před %n hodinami","před %n hodinami","před %n hodinami"], + "_in %n minute_::_in %n minutes_" : ["během %n minuty","během %n minut","během %n minut","během %n minut"], + "_%n minute ago_::_%n minutes ago_" : ["před minutou","před %n minutami","před %n minutami","před %n minutami"], + "in a few seconds" : "během několika sekund", + "seconds ago" : "před pár sekundami", + "Empty file" : "Prázdný soubor", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s identifikátorem: %s neexistuje. Povolte ho v nastavení aplikací, nebo se obraťte na správce.", + "File name is a reserved word" : "Název souboru je rezervované slovo", + "File name contains at least one invalid character" : "Název souboru obsahuje nejméně jeden neplatný znak", + "File name is too long" : "Název souboru je příliš dlouhý", + "Dot files are not allowed" : "Názvy souborů, začínající na tečku nejsou povolené", + "Empty filename is not allowed" : "Je třeba vyplnit název souboru", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikace „%s“ nemůže být nainstalována protože soubor appinfo nelze přečíst.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikaci „%s“ nelze nainstalovat, protože není kompatibilní s touto verzí serveru.", + "__language_name__" : "čeština", + "This is an automatically sent email, please do not reply." : "Toto je automaticky odesílaný e-mail, neodpovídejte na něj.", + "Help" : "Nápověda", + "Apps" : "Aplikace", + "Settings" : "Nastavení", + "Log out" : "Odhlásit se", + "Users" : "Uživatelé", + "Unknown user" : "Neznámý uživatel", + "Additional settings" : "Další nastavení", + "%s enter the database username and name." : "%s zadejte databázové uživatelské jméno a jméno.", + "%s enter the database username." : "Zadejte uživatelské jméno %s databáze.", + "%s enter the database name." : "Zadejte název databáze pro %s databáze.", + "%s you may not use dots in the database name" : "V názvu databáze %s není možné používat tečky.", + "MySQL username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do MySQL", + "You need to enter details of an existing account." : "Je třeba zadat podrobnosti existujícího účtu.", + "Oracle connection could not be established" : "Spojení s Oracle nemohlo být navázáno", + "Oracle username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do Oracle", + "PostgreSQL username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "macOS není podporován a %s nebude na této platformě správně fungovat. Používejte pouze na vlastní nebezpečí!", + "For the best results, please consider using a GNU/Linux server instead." : "Místo toho zvažte pro nejlepší funkčnost použití GNU/Linux serveru.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Zdá se, že tato instance %s je provozována v 32-bitovém PHP prostředí a v php.ini je nastavena volba open_basedir. Toto povede k problémům se soubory většími než 4 GB a silně není doporučováno.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Odstraňte z php.ini nastavení volby open_basedir nebo přejděte na 64-bitové PHP.", + "Set an admin username." : "Nastavte uživatelské jméno správce.", + "Set an admin password." : "Nastavte heslo pro účet správce.", + "Can't create or write into the data directory %s" : "Nedaří se vytvořit nebo zapisovat do datového adresáře %s", + "Invalid Federated Cloud ID" : "Neplatný identifikátor v rámci federovaného cloudu", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Úložiště pro sdílení %s musí implementovat rozhraní OCP\\Share_Backend", + "Sharing backend %s not found" : "Úložiště sdílení %s nenalezeno", + "Sharing backend for %s not found" : "Úložiště sdílení pro %s nenalezeno", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s sdílí „%2$s“ a dodává:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s sdílí „%2$s“ a dodává", + "»%s« added a note to a file shared with you" : "„%s“ dodává poznámku k nasdílenému souboru ", + "Open »%s«" : "Otevřít „%s“", + "%1$s via %2$s" : "%1$s prostřednictvím %2$s", + "You are not allowed to share %s" : "Nemáte povoleno sdílet %s", + "Can’t increase permissions of %s" : "Nelze zvýšit oprávnění %s", + "Files can’t be shared with delete permissions" : "Soubory nelze sdílet s oprávněními k odstranění", + "Files can’t be shared with create permissions" : "Soubory nelze sdílet s oprávněními k vytváření", + "Expiration date is in the past" : "Datum skončení platnosti je v minulosti", + "Can’t set expiration date more than %s days in the future" : "Nelze nastavit datum skončení platnosti více než %s dní v budoucnu", + "%1$s shared »%2$s« with you" : "%1$s s vámi sdílí „%2$s“", + "%1$s shared »%2$s« with you." : "%1$s vám nasdílel(a) „%2$s“.", + "Click the button below to open it." : "Pro otevření klikněte na tlačítko níže.", + "The requested share does not exist anymore" : "Požadované sdílení už neexistuje", + "Could not find category \"%s\"" : "Nedaří se nalézt kategorii „%s“", + "Sunday" : "neděle", + "Monday" : "pondělí", + "Tuesday" : "úterý", + "Wednesday" : "středa", + "Thursday" : "čtvrtek", + "Friday" : "pátek", + "Saturday" : "sobota", + "Sun." : "ne", + "Mon." : "po", + "Tue." : "út", + "Wed." : "st", + "Thu." : "čt", + "Fri." : "pá", + "Sat." : "so", + "Su" : "ne", + "Mo" : "po", + "Tu" : "út", + "We" : "st", + "Th" : "čt", + "Fr" : "pá", + "Sa" : "so", + "January" : "leden", + "February" : "únor", + "March" : "březen", + "April" : "duben", + "May" : "květen", + "June" : "červen", + "July" : "červenec", + "August" : "srpen", + "September" : "září", + "October" : "říjen", + "November" : "listopad", + "December" : "prosinec", + "Jan." : "led.", + "Feb." : "úno.", + "Mar." : "bře.", + "Apr." : "dub.", + "May." : "kvě.", + "Jun." : "čvn.", + "Jul." : "čvc.", + "Aug." : "srp.", + "Sep." : "zář.", + "Oct." : "říj.", + "Nov." : "list.", + "Dec." : "pro.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Pouze následující znaky jsou povoleny pro uživatelské jméno: „a-z“, „A-Z“, „0-9“, a „_.@-'“", + "A valid username must be provided" : "Je třeba zadat platné uživatelské jméno", + "Username contains whitespace at the beginning or at the end" : "Uživatelské jméno obsahuje mezery na svém začátku nebo konci", + "Username must not consist of dots only" : "Uživatelské jméno se nemůže skládat pouze ze samých teček", + "Username is invalid because files already exist for this user" : "Uživatelské jméno není platné, protože protože pro tohoto uživatele už existují soubory", + "A valid password must be provided" : "Je třeba zadat platné heslo", + "The username is already being used" : "Uživatelské jméno už je využíváno", + "Could not create user" : "Nepodařilo se vytvořit uživatele", + "User disabled" : "Uživatel zakázán", + "Login canceled by app" : "Přihlášení zrušeno aplikací", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikaci „%1$s“ nelze nainstalovat, protože nejsou splněny následující závislosti: %2$s", + "a safe home for all your data" : "bezpečný domov pro všechna vaše data", + "File is currently busy, please try again later" : "Soubor je používán, zkuste to později", + "Can't read file" : "Nelze přečíst soubor", + "Application is not enabled" : "Aplikace není povolena", + "Authentication error" : "Chyba při ověřování se", + "Token expired. Please reload page." : "Platnost tokenu skončila. Načtěte stránku znovu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nejsou instalovány ovladače databází (sqlite, mysql nebo postresql).", + "Cannot write into \"config\" directory" : "Nelze zapisovat do adresáře „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do kořenové složky webu pro účet, pod kterým je provozován webový server. Viz %s", + "Cannot write into \"apps\" directory" : "Nedaří se zapisovat do adresáře „apps“", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Toto je obvykle možné napravit udělením přístupu ke čtení adresáře s aplikací pro webový server nebo vypnutím katalogu s aplikacemi v souboru s nastaveními.", + "Cannot create \"data\" directory" : "Nedaří se vytvořit adresář „data“", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do kořenové složky webu pro účet, pod kterým je provozován webový server. Viz %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Oprávnění lze obvykle napravit umožněním zápisu do kořene webu pro účet, pod kterým je provozován webový server. Viz %s.", + "Setting locale to %s failed" : "Nastavení jazyka na %s se nezdařilo", + "Please install one of these locales on your system and restart your webserver." : "Do svého systému nainstalujte alespoň jeden z těchto jazyků a restartujte webový server.", + "PHP module %s not installed." : "PHP modul %s není nainstalován.", + "Please ask your server administrator to install the module." : "Požádejte správce serveru, který využíváte o instalaci tohoto modulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Hodnota PHP nastavení „%s“ není nastavená na „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Úprava tohoto nastavení v php.ini umožní Nextcloud opět zprovoznit", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je nastaven na „%s“ namísto očekávané hodnoty „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pro nápravu nastavte v souboru php.ini parametr mbstring.func_overload na 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Je požadována verze softwarové knihovny libxml2 minimálně 2.7.0. Nyní je nainstalována verze %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pro opravu tohoto problému aktualizujte knihovnu libxml2 a restartujte webový server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?", + "Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.", + "PostgreSQL >= 9 required" : "Je vyžadováno PostgreSQL verze 9 a novější", + "Please upgrade your database version" : "Aktualizujte verzi své databáze", + "Your data directory is readable by other users" : "Váš adresář data je čitelný ostatním uživatelům", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Změňte práva na 0770, aby obsah adresáře nemohl být vypisován ostatními uživateli.", + "Your data directory must be an absolute path" : "Je třeba, aby váš adresář data byl zadán jako úplný popis umístění", + "Check the value of \"datadirectory\" in your configuration" : "Zkontrolujte hodnotu „datadirectory“ ve svém nastavení", + "Your data directory is invalid" : "Váš adresář data není plantý", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ověřte, že v kořeni datového adresáře je soubor s názvem „.ocdata“.", + "Action \"%s\" not supported or implemented." : "Akce „%s“ není podporována nebo implementována.", + "Authentication failed, wrong token or provider ID given" : "Ověření se nezdařilo, předán chybný token nebo identifikátor poskytovatele", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Pro dokončení požadavku chybí parametry. Konkrétně: „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Identifikátor „%1$s“ už je používán poskytovatelem federování cloudu „%2$s“", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Poskytovatel federování cloudů s identifikátorem: „%s“ neexistuje", + "Could not obtain lock type %d on \"%s\"." : "Nedaří získat zámek typu %d na „%s“.", + "Storage unauthorized. %s" : "Úložiště neověřeno. %s", + "Storage incomplete configuration. %s" : "Neúplné nastavení pro úložiště. %s", + "Storage connection error. %s" : "Chyba připojení úložiště. %s", + "Storage is temporarily not available" : "Úložiště je dočasně nedostupné", + "Storage connection timeout. %s" : "Překročen časový limit připojování k úložišti. %s", + "Following databases are supported: %s" : "Jsou podporovány následující databáze: %s", + "Following platforms are supported: %s" : "Jsou podporovány následující systémy: %s", + "Overview" : "Přehled", + "Basic settings" : "Základní nastavení", + "Sharing" : "Sdílení", + "Security" : "Zabezpečení", + "Groupware" : "Software pro podporu spolupráce", + "Personal info" : "Osobní údaje", + "Mobile & desktop" : "Mobilní a desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "To lze obvykle vyřešit povolením zápisu webovému serveru do adresáře apps nebo zakázáním appstore v souboru s nastaveními. Viz %s" +}, +"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/cs.json b/docker/overlays/nextcloud/html/lib/l10n/cs.json new file mode 100644 index 0000000..499a55e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/cs.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nedaří se zapisovat do adresáře „config“!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do adresáře s nastaveními pro účet, pod kterým je provozován webový server", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Nebo, pokud chcete mít soubor config.php pouze pro čtení, nastavte v něm volbu „config_is_read_only“ na hodnotu true.", + "See %s" : "Viz %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Toto je obvykle možné vyřešit udělením webovému serveru oprávnění k zápisu do adresáře s nastaveními.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Nebo, pokud chcete mít soubor config.php pouze pro čtení, nastavte v něm volbu „config_is_read_only“ na hodnotu true. Viz %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.", + "Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci", + "Other activities" : "Ostatní aktivity", + "%1$s and %2$s" : "%1$s a %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s a %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s a %5$s", + "Education Edition" : "Vydání pro vzdělávací instituce", + "Enterprise bundle" : "Sada pro organizace", + "Groupware bundle" : "Sada pro podporu spolupráce", + "Hub bundle" : "Sada pro centrum aktivity (hub)", + "Social sharing bundle" : "Balíček pro sdílení na sociálních sítích", + "PHP %s or higher is required." : "Je vyžadováno PHP %s nebo novější.", + "PHP with a version lower than %s is required." : "Je vyžadováno PHP ve verzi nižší než %s.", + "%sbit or higher PHP required." : "Je vyžadováno PHP %sbit nebo vyšší.", + "The following architectures are supported: %s" : "Jsou podporovány následující architektury: %s", + "The following databases are supported: %s" : "Jsou podporovány následující databáze: %s", + "The command line tool %s could not be found" : "Nástroj příkazového řádku %s nebyl nalezen", + "The library %s is not available." : "Knihovna %s není dostupná.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Je vyžadována knihovna %1$s novější verze než %2$s – verze k dispozici je %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Je vyžadována knihovna %1$s verzi nižší než %2$s – dostupná verze %3$s.", + "The following platforms are supported: %s" : "Jsou podporovány následující systémy: %s", + "Server version %s or higher is required." : "Je potřeba verze serveru %s nebo novější.", + "Server version %s or lower is required." : "Je potřeba verze serveru %s nebo starší.", + "Logged in user must be an admin or sub admin" : "Je třeba, aby přihlášený uživatel byl správcem či správcem pro dílčí oblast", + "Logged in user must be an admin" : "Je třeba, aby přihlášený uživatel byl správce", + "Wiping of device %s has started" : "Vymazávání zařízení %s zahájeno", + "Wiping of device »%s« has started" : "Vymazávání zařízení „%s“ zahájeno", + "»%s« started remote wipe" : "„%s“ zahájilo vymazávání na dálku", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Přístroj či aplikace »%s« spustila proces vzdáleného vymazání. Obdržíte další e-mail poté co bude proces ukončen", + "Wiping of device %s has finished" : "Vymazávání zařízení %s dokončeno", + "Wiping of device »%s« has finished" : "Vymazávání zařízení „%s“ dokončeno", + "»%s« finished remote wipe" : "„%s“ dokončilo vymazání na dálku", + "Device or application »%s« has finished the remote wipe process." : "Přístroj či aplikace „%s“ dokončila proces vymazání na dálku.", + "Remote wipe started" : "Vymazání na dálku zahájeno", + "A remote wipe was started on device %s" : "Na zařízení %s bylo spuštěno vymazání na dálku", + "Remote wipe finished" : "Vymazání na dálku dokončeno", + "The remote wipe on %s has finished" : "Vymazání %s na dálku dokončeno", + "Authentication" : "Ověření", + "Unknown filetype" : "Neznámý typ souboru", + "Invalid image" : "Neplatný obrázek", + "Avatar image is not square" : "Obrázek s avatarem není čtvercový", + "today" : "dnes", + "tomorrow" : "zítra", + "yesterday" : "včera", + "_in %n day_::_in %n days_" : ["během %n dne","během %n dnů","během %n dnů","během %n dnů"], + "_%n day ago_::_%n days ago_" : ["včera","před %n dny","před %n dny","před %n dny"], + "next month" : "následující měsíc", + "last month" : "minulý měsíc", + "_in %n month_::_in %n months_" : ["během %n měsíce","během %n měsíců","během %n měsíců","během %n měsíců"], + "_%n month ago_::_%n months ago_" : ["před %n měsícem","před %n měsíci","před %n měsíci","před %n měsíci"], + "next year" : "následující rok", + "last year" : "minulý rok", + "_in %n year_::_in %n years_" : ["během %n roku","během %n roků","během %n roků","během %n roků"], + "_%n year ago_::_%n years ago_" : ["před rokem","před %n lety","před %n lety","před %n lety"], + "_in %n hour_::_in %n hours_" : ["během %n hodiny","během %n hodin","během %n hodin","během %n hodin"], + "_%n hour ago_::_%n hours ago_" : ["před %n hodinou","před %n hodinami","před %n hodinami","před %n hodinami"], + "_in %n minute_::_in %n minutes_" : ["během %n minuty","během %n minut","během %n minut","během %n minut"], + "_%n minute ago_::_%n minutes ago_" : ["před minutou","před %n minutami","před %n minutami","před %n minutami"], + "in a few seconds" : "během několika sekund", + "seconds ago" : "před pár sekundami", + "Empty file" : "Prázdný soubor", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s identifikátorem: %s neexistuje. Povolte ho v nastavení aplikací, nebo se obraťte na správce.", + "File name is a reserved word" : "Název souboru je rezervované slovo", + "File name contains at least one invalid character" : "Název souboru obsahuje nejméně jeden neplatný znak", + "File name is too long" : "Název souboru je příliš dlouhý", + "Dot files are not allowed" : "Názvy souborů, začínající na tečku nejsou povolené", + "Empty filename is not allowed" : "Je třeba vyplnit název souboru", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikace „%s“ nemůže být nainstalována protože soubor appinfo nelze přečíst.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikaci „%s“ nelze nainstalovat, protože není kompatibilní s touto verzí serveru.", + "__language_name__" : "čeština", + "This is an automatically sent email, please do not reply." : "Toto je automaticky odesílaný e-mail, neodpovídejte na něj.", + "Help" : "Nápověda", + "Apps" : "Aplikace", + "Settings" : "Nastavení", + "Log out" : "Odhlásit se", + "Users" : "Uživatelé", + "Unknown user" : "Neznámý uživatel", + "Additional settings" : "Další nastavení", + "%s enter the database username and name." : "%s zadejte databázové uživatelské jméno a jméno.", + "%s enter the database username." : "Zadejte uživatelské jméno %s databáze.", + "%s enter the database name." : "Zadejte název databáze pro %s databáze.", + "%s you may not use dots in the database name" : "V názvu databáze %s není možné používat tečky.", + "MySQL username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do MySQL", + "You need to enter details of an existing account." : "Je třeba zadat podrobnosti existujícího účtu.", + "Oracle connection could not be established" : "Spojení s Oracle nemohlo být navázáno", + "Oracle username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do Oracle", + "PostgreSQL username and/or password not valid" : "Neplatné uživatelské jméno a/nebo heslo do PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "macOS není podporován a %s nebude na této platformě správně fungovat. Používejte pouze na vlastní nebezpečí!", + "For the best results, please consider using a GNU/Linux server instead." : "Místo toho zvažte pro nejlepší funkčnost použití GNU/Linux serveru.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Zdá se, že tato instance %s je provozována v 32-bitovém PHP prostředí a v php.ini je nastavena volba open_basedir. Toto povede k problémům se soubory většími než 4 GB a silně není doporučováno.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Odstraňte z php.ini nastavení volby open_basedir nebo přejděte na 64-bitové PHP.", + "Set an admin username." : "Nastavte uživatelské jméno správce.", + "Set an admin password." : "Nastavte heslo pro účet správce.", + "Can't create or write into the data directory %s" : "Nedaří se vytvořit nebo zapisovat do datového adresáře %s", + "Invalid Federated Cloud ID" : "Neplatný identifikátor v rámci federovaného cloudu", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Úložiště pro sdílení %s musí implementovat rozhraní OCP\\Share_Backend", + "Sharing backend %s not found" : "Úložiště sdílení %s nenalezeno", + "Sharing backend for %s not found" : "Úložiště sdílení pro %s nenalezeno", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s sdílí „%2$s“ a dodává:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s sdílí „%2$s“ a dodává", + "»%s« added a note to a file shared with you" : "„%s“ dodává poznámku k nasdílenému souboru ", + "Open »%s«" : "Otevřít „%s“", + "%1$s via %2$s" : "%1$s prostřednictvím %2$s", + "You are not allowed to share %s" : "Nemáte povoleno sdílet %s", + "Can’t increase permissions of %s" : "Nelze zvýšit oprávnění %s", + "Files can’t be shared with delete permissions" : "Soubory nelze sdílet s oprávněními k odstranění", + "Files can’t be shared with create permissions" : "Soubory nelze sdílet s oprávněními k vytváření", + "Expiration date is in the past" : "Datum skončení platnosti je v minulosti", + "Can’t set expiration date more than %s days in the future" : "Nelze nastavit datum skončení platnosti více než %s dní v budoucnu", + "%1$s shared »%2$s« with you" : "%1$s s vámi sdílí „%2$s“", + "%1$s shared »%2$s« with you." : "%1$s vám nasdílel(a) „%2$s“.", + "Click the button below to open it." : "Pro otevření klikněte na tlačítko níže.", + "The requested share does not exist anymore" : "Požadované sdílení už neexistuje", + "Could not find category \"%s\"" : "Nedaří se nalézt kategorii „%s“", + "Sunday" : "neděle", + "Monday" : "pondělí", + "Tuesday" : "úterý", + "Wednesday" : "středa", + "Thursday" : "čtvrtek", + "Friday" : "pátek", + "Saturday" : "sobota", + "Sun." : "ne", + "Mon." : "po", + "Tue." : "út", + "Wed." : "st", + "Thu." : "čt", + "Fri." : "pá", + "Sat." : "so", + "Su" : "ne", + "Mo" : "po", + "Tu" : "út", + "We" : "st", + "Th" : "čt", + "Fr" : "pá", + "Sa" : "so", + "January" : "leden", + "February" : "únor", + "March" : "březen", + "April" : "duben", + "May" : "květen", + "June" : "červen", + "July" : "červenec", + "August" : "srpen", + "September" : "září", + "October" : "říjen", + "November" : "listopad", + "December" : "prosinec", + "Jan." : "led.", + "Feb." : "úno.", + "Mar." : "bře.", + "Apr." : "dub.", + "May." : "kvě.", + "Jun." : "čvn.", + "Jul." : "čvc.", + "Aug." : "srp.", + "Sep." : "zář.", + "Oct." : "říj.", + "Nov." : "list.", + "Dec." : "pro.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Pouze následující znaky jsou povoleny pro uživatelské jméno: „a-z“, „A-Z“, „0-9“, a „_.@-'“", + "A valid username must be provided" : "Je třeba zadat platné uživatelské jméno", + "Username contains whitespace at the beginning or at the end" : "Uživatelské jméno obsahuje mezery na svém začátku nebo konci", + "Username must not consist of dots only" : "Uživatelské jméno se nemůže skládat pouze ze samých teček", + "Username is invalid because files already exist for this user" : "Uživatelské jméno není platné, protože protože pro tohoto uživatele už existují soubory", + "A valid password must be provided" : "Je třeba zadat platné heslo", + "The username is already being used" : "Uživatelské jméno už je využíváno", + "Could not create user" : "Nepodařilo se vytvořit uživatele", + "User disabled" : "Uživatel zakázán", + "Login canceled by app" : "Přihlášení zrušeno aplikací", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikaci „%1$s“ nelze nainstalovat, protože nejsou splněny následující závislosti: %2$s", + "a safe home for all your data" : "bezpečný domov pro všechna vaše data", + "File is currently busy, please try again later" : "Soubor je používán, zkuste to později", + "Can't read file" : "Nelze přečíst soubor", + "Application is not enabled" : "Aplikace není povolena", + "Authentication error" : "Chyba při ověřování se", + "Token expired. Please reload page." : "Platnost tokenu skončila. Načtěte stránku znovu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nejsou instalovány ovladače databází (sqlite, mysql nebo postresql).", + "Cannot write into \"config\" directory" : "Nelze zapisovat do adresáře „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do kořenové složky webu pro účet, pod kterým je provozován webový server. Viz %s", + "Cannot write into \"apps\" directory" : "Nedaří se zapisovat do adresáře „apps“", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Toto je obvykle možné napravit udělením přístupu ke čtení adresáře s aplikací pro webový server nebo vypnutím katalogu s aplikacemi v souboru s nastaveními.", + "Cannot create \"data\" directory" : "Nedaří se vytvořit adresář „data“", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Toto obvykle lze vyřešit udělením oprávnění k zápisu do kořenové složky webu pro účet, pod kterým je provozován webový server. Viz %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Oprávnění lze obvykle napravit umožněním zápisu do kořene webu pro účet, pod kterým je provozován webový server. Viz %s.", + "Setting locale to %s failed" : "Nastavení jazyka na %s se nezdařilo", + "Please install one of these locales on your system and restart your webserver." : "Do svého systému nainstalujte alespoň jeden z těchto jazyků a restartujte webový server.", + "PHP module %s not installed." : "PHP modul %s není nainstalován.", + "Please ask your server administrator to install the module." : "Požádejte správce serveru, který využíváte o instalaci tohoto modulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Hodnota PHP nastavení „%s“ není nastavená na „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Úprava tohoto nastavení v php.ini umožní Nextcloud opět zprovoznit", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je nastaven na „%s“ namísto očekávané hodnoty „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pro nápravu nastavte v souboru php.ini parametr mbstring.func_overload na 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Je požadována verze softwarové knihovny libxml2 minimálně 2.7.0. Nyní je nainstalována verze %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pro opravu tohoto problému aktualizujte knihovnu libxml2 a restartujte webový server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?", + "Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.", + "PostgreSQL >= 9 required" : "Je vyžadováno PostgreSQL verze 9 a novější", + "Please upgrade your database version" : "Aktualizujte verzi své databáze", + "Your data directory is readable by other users" : "Váš adresář data je čitelný ostatním uživatelům", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Změňte práva na 0770, aby obsah adresáře nemohl být vypisován ostatními uživateli.", + "Your data directory must be an absolute path" : "Je třeba, aby váš adresář data byl zadán jako úplný popis umístění", + "Check the value of \"datadirectory\" in your configuration" : "Zkontrolujte hodnotu „datadirectory“ ve svém nastavení", + "Your data directory is invalid" : "Váš adresář data není plantý", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ověřte, že v kořeni datového adresáře je soubor s názvem „.ocdata“.", + "Action \"%s\" not supported or implemented." : "Akce „%s“ není podporována nebo implementována.", + "Authentication failed, wrong token or provider ID given" : "Ověření se nezdařilo, předán chybný token nebo identifikátor poskytovatele", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Pro dokončení požadavku chybí parametry. Konkrétně: „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Identifikátor „%1$s“ už je používán poskytovatelem federování cloudu „%2$s“", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Poskytovatel federování cloudů s identifikátorem: „%s“ neexistuje", + "Could not obtain lock type %d on \"%s\"." : "Nedaří získat zámek typu %d na „%s“.", + "Storage unauthorized. %s" : "Úložiště neověřeno. %s", + "Storage incomplete configuration. %s" : "Neúplné nastavení pro úložiště. %s", + "Storage connection error. %s" : "Chyba připojení úložiště. %s", + "Storage is temporarily not available" : "Úložiště je dočasně nedostupné", + "Storage connection timeout. %s" : "Překročen časový limit připojování k úložišti. %s", + "Following databases are supported: %s" : "Jsou podporovány následující databáze: %s", + "Following platforms are supported: %s" : "Jsou podporovány následující systémy: %s", + "Overview" : "Přehled", + "Basic settings" : "Základní nastavení", + "Sharing" : "Sdílení", + "Security" : "Zabezpečení", + "Groupware" : "Software pro podporu spolupráce", + "Personal info" : "Osobní údaje", + "Mobile & desktop" : "Mobilní a desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "To lze obvykle vyřešit povolením zápisu webovému serveru do adresáře apps nebo zakázáním appstore v souboru s nastaveními. Viz %s" +},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/cy_GB.js b/docker/overlays/nextcloud/html/lib/l10n/cy_GB.js new file mode 100644 index 0000000..00cd373 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/cy_GB.js @@ -0,0 +1,65 @@ +OC.L10N.register( + "lib", + { + "today" : "heddiw", + "yesterday" : "ddoe", + "last month" : "mis diwethaf", + "last year" : "y llynedd", + "seconds ago" : "eiliad yn ôl", + "Help" : "Cymorth", + "Apps" : "Pecynnau", + "Settings" : "Gosodiadau", + "Log out" : "Allgofnodi", + "Users" : "Defnyddwyr", + "%s enter the database username." : "%s rhowch enw defnyddiwr y gronfa ddata.", + "%s enter the database name." : "%s rhowch enw'r gronfa ddata.", + "%s you may not use dots in the database name" : "%s does dim hawl defnyddio dot yn enw'r gronfa ddata", + "Oracle username and/or password not valid" : "Enw a/neu gyfrinair Oracle annilys", + "PostgreSQL username and/or password not valid" : "Enw a/neu gyfrinair PostgreSQL annilys", + "Set an admin username." : "Creu enw defnyddiwr i'r gweinyddwr.", + "Set an admin password." : "Gosod cyfrinair y gweinyddwr.", + "Open »%s«" : "Agor »%s«", + "Could not find category \"%s\"" : "Methu canfod categori \"%s\"", + "Sunday" : "Sul", + "Monday" : "Llun", + "Tuesday" : "Mawrth", + "Wednesday" : "Mercher", + "Thursday" : "Iau", + "Friday" : "Gwener", + "Saturday" : "Sadwrn", + "Sun." : "Sul.", + "Mon." : "Llun.", + "Tue." : "Maw.", + "Wed." : "Mer.", + "Thu." : "Iau.", + "Fri." : "Gwe.", + "Sat." : "Sad.", + "January" : "Ionawr", + "February" : "Chwefror", + "March" : "Mawrth", + "April" : "Ebrill", + "May" : "Mai", + "June" : "Mehefin", + "July" : "Gorffennaf", + "August" : "Awst", + "September" : "Medi", + "October" : "Hydref", + "November" : "Tachwedd", + "December" : "Rhagfyr", + "Jan." : "Ion.", + "Feb." : "Chwe.", + "Mar." : "Maw.", + "Apr." : "Ebr.", + "May." : "Mai.", + "Jun." : "Meh.", + "Jul." : "Gor.", + "Aug." : "Aws.", + "Sep." : "Med.", + "Oct." : "Hyd.", + "Nov." : "Tach.", + "Dec." : "Rhag.", + "Application is not enabled" : "Nid yw'r pecyn wedi'i alluogi", + "Authentication error" : "Gwall dilysu", + "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen." +}, +"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/cy_GB.json b/docker/overlays/nextcloud/html/lib/l10n/cy_GB.json new file mode 100644 index 0000000..02902e0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/cy_GB.json @@ -0,0 +1,63 @@ +{ "translations": { + "today" : "heddiw", + "yesterday" : "ddoe", + "last month" : "mis diwethaf", + "last year" : "y llynedd", + "seconds ago" : "eiliad yn ôl", + "Help" : "Cymorth", + "Apps" : "Pecynnau", + "Settings" : "Gosodiadau", + "Log out" : "Allgofnodi", + "Users" : "Defnyddwyr", + "%s enter the database username." : "%s rhowch enw defnyddiwr y gronfa ddata.", + "%s enter the database name." : "%s rhowch enw'r gronfa ddata.", + "%s you may not use dots in the database name" : "%s does dim hawl defnyddio dot yn enw'r gronfa ddata", + "Oracle username and/or password not valid" : "Enw a/neu gyfrinair Oracle annilys", + "PostgreSQL username and/or password not valid" : "Enw a/neu gyfrinair PostgreSQL annilys", + "Set an admin username." : "Creu enw defnyddiwr i'r gweinyddwr.", + "Set an admin password." : "Gosod cyfrinair y gweinyddwr.", + "Open »%s«" : "Agor »%s«", + "Could not find category \"%s\"" : "Methu canfod categori \"%s\"", + "Sunday" : "Sul", + "Monday" : "Llun", + "Tuesday" : "Mawrth", + "Wednesday" : "Mercher", + "Thursday" : "Iau", + "Friday" : "Gwener", + "Saturday" : "Sadwrn", + "Sun." : "Sul.", + "Mon." : "Llun.", + "Tue." : "Maw.", + "Wed." : "Mer.", + "Thu." : "Iau.", + "Fri." : "Gwe.", + "Sat." : "Sad.", + "January" : "Ionawr", + "February" : "Chwefror", + "March" : "Mawrth", + "April" : "Ebrill", + "May" : "Mai", + "June" : "Mehefin", + "July" : "Gorffennaf", + "August" : "Awst", + "September" : "Medi", + "October" : "Hydref", + "November" : "Tachwedd", + "December" : "Rhagfyr", + "Jan." : "Ion.", + "Feb." : "Chwe.", + "Mar." : "Maw.", + "Apr." : "Ebr.", + "May." : "Mai.", + "Jun." : "Meh.", + "Jul." : "Gor.", + "Aug." : "Aws.", + "Sep." : "Med.", + "Oct." : "Hyd.", + "Nov." : "Tach.", + "Dec." : "Rhag.", + "Application is not enabled" : "Nid yw'r pecyn wedi'i alluogi", + "Authentication error" : "Gwall dilysu", + "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen." +},"pluralForm" :"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/da.js b/docker/overlays/nextcloud/html/lib/l10n/da.js new file mode 100644 index 0000000..b6e578c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/da.js @@ -0,0 +1,169 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Kan ikke skrive til mappen \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dette kan som regel ordnes ved at give webserveren skrive adgang til config mappen", + "See %s" : "Se %s", + "Sample configuration detected" : "Eksempel for konfiguration registreret", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Der er registreret at eksempel for konfiguration er blevet kopieret. Dette kan ødelægge din installation og understøttes ikke. Læs venligst dokumentationen før der foretages ændringer i config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "PHP %s or higher is required." : "Der kræves PHP %s eller nyere.", + "PHP with a version lower than %s is required." : "Der kræves PHP %s eller ældre.", + "The command line tool %s could not be found" : "Kommandolinjeværktøjet %s blev ikke fundet", + "The library %s is not available." : "Biblioteket %s er ikke tilgængeligt.", + "Server version %s or higher is required." : "Du skal have server version %s eller nyere.", + "Server version %s or lower is required." : "Du skal have server version %s eller ældre.", + "Authentication" : "Godkendelse", + "Unknown filetype" : "Ukendt filtype", + "Invalid image" : "Ugyldigt billede", + "today" : "i dag", + "tomorrow" : "i morgen", + "yesterday" : "i går", + "_in %n day_::_in %n days_" : ["om %n dag","om %n dage"], + "_%n day ago_::_%n days ago_" : ["%n dag siden","%n dage siden"], + "next month" : "næste måned", + "last month" : "sidste måned", + "_in %n month_::_in %n months_" : ["om %n måned","om %n måneder"], + "_%n month ago_::_%n months ago_" : ["%n måned siden","%n måneder siden"], + "next year" : "næste år", + "last year" : "sidste år", + "_in %n year_::_in %n years_" : ["om %n år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år siden","%n år siden"], + "_in %n hour_::_in %n hours_" : ["om %n time","om %n timer"], + "_%n hour ago_::_%n hours ago_" : ["%n time siden","%n timer siden"], + "_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minutter"], + "_%n minute ago_::_%n minutes ago_" : ["%n minut siden","%n minutter siden"], + "in a few seconds" : "om få sekunder", + "seconds ago" : "sekunder siden", + "File name is a reserved word" : "Filnavnet er et reserveret ord", + "File name contains at least one invalid character" : "Filnavnet indeholder mindst ét ugyldigt tegn", + "File name is too long" : "Filnavnet er for langt", + "Dot files are not allowed" : "Filer med punktummer er ikke tilladt", + "Empty filename is not allowed" : "Tomme filnavne er ikke tilladt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan ikke installeres fordi appinfo filen ikke kan læses.", + "__language_name__" : "Dansk", + "Help" : "Hjælp", + "Apps" : "Apps", + "Settings" : "Indstillinger", + "Log out" : "Log ud", + "Users" : "Brugere", + "Unknown user" : "Ukendt bruger", + "Additional settings" : "Yderligere indstillinger", + "%s enter the database username and name." : "%s indtast brugernavn og navn til databasen.", + "%s enter the database username." : "%s indtast database brugernavnet.", + "%s enter the database name." : "%s indtast database navnet.", + "%s you may not use dots in the database name" : "%s du må ikke bruge punktummer i databasenavnet.", + "Oracle connection could not be established" : "Oracle forbindelsen kunne ikke etableres", + "Oracle username and/or password not valid" : "Oracle brugernavn og/eller kodeord er ikke gyldigt.", + "PostgreSQL username and/or password not valid" : "PostgreSQL brugernavn og/eller kodeord er ikke gyldigt.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X understøttes ikke og %s vil ikke virke optimalt på denne platform. Anvend på eget ansvar!", + "For the best results, please consider using a GNU/Linux server instead." : "For de bedste resultater, overvej venligst at bruge en GNU/Linux-server i stedet.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det ser ud til, at denne %s-instans kører på et 32-bit PHP-miljø, samt at open_basedir er blevet konfigureret gennem php.ini. Dette vil føre til problemer med filer som er større end 4GB, og frarådes kraftigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjern venligst indstillingen for open_basedir inde i din php.ini eller skift til 64-bit PHP.", + "Set an admin username." : "Angiv et admin brugernavn.", + "Set an admin password." : "Angiv et admin kodeord.", + "Can't create or write into the data directory %s" : "Kan ikke oprette eller skrive ind i datamappen %s", + "Invalid Federated Cloud ID" : "Ugyldigt Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delingsbackend'en %s skal implementere grænsefladen OCP\\Share_Backend", + "Sharing backend %s not found" : "Delingsbackend'en %s blev ikke fundet", + "Sharing backend for %s not found" : "Delingsbackend'en for %s blev ikke fundet", + "Open »%s«" : "Åbn »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har ikke tilladelse til at dele %s", + "Expiration date is in the past" : "Udløbsdatoen ligger tilbage i tid", + "Click the button below to open it." : "Klik på knappen nedenunder for at åbne.", + "Could not find category \"%s\"" : "Kunne ikke finde kategorien \"%s\"", + "Sunday" : "Søndag", + "Monday" : "Mandag", + "Tuesday" : "Tirsdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lørdag", + "Sun." : "Søn.", + "Mon." : "Man.", + "Tue." : "Tir.", + "Wed." : ".", + "Thu." : "Tor.", + "Fri." : "Fre.", + "Sat." : "Lør.", + "Su" : "Sø", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lø", + "January" : "Januar", + "February" : "Februar", + "March" : "Marts", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "eb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kun følgende tegn kan indgå i et brugernavn: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Et gyldigt brugernavn skal angives", + "Username contains whitespace at the beginning or at the end" : "Brugernavnet har et mellemrum i starten eller slutningen", + "A valid password must be provided" : "En gyldig adgangskode skal angives", + "The username is already being used" : "Brugernavnet er allerede i brug", + "User disabled" : "Bruger deaktiveret", + "Login canceled by app" : "Login annulleret af app", + "a safe home for all your data" : "et sikkert hjem til alle dine data", + "File is currently busy, please try again later" : "Filen er i øjeblikket optaget - forsøg igen senere", + "Can't read file" : "Kan ikke læse filen", + "Application is not enabled" : "Programmet er ikke aktiveret", + "Authentication error" : "Adgangsfejl", + "Token expired. Please reload page." : "Adgang er udløbet. Genindlæs siden.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ingen database driver (sqlite, mysql eller postgresql) er installeret.", + "Cannot write into \"config\" directory" : "Kan ikke skrive til mappen \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dette kan som regel ordnes ved at give webserveren skrive adgang til config mappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan ikke skrive til mappen \"apps\"", + "Setting locale to %s failed" : "Angivelse af %s for lokalitet mislykkedes", + "Please install one of these locales on your system and restart your webserver." : "Installér venligst én af disse lokaliteter på dit system, og genstart din webserver.", + "PHP module %s not installed." : "PHP-modulet %s er ikke installeret.", + "Please ask your server administrator to install the module." : "Du bedes anmode din serveradministrator om at installere modulet.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-indstillingen \"%s\" er ikke angivet til \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er angivet til \"%s\", i stedet for den forventede værdi \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "For at rette dette problem, angiv\nmbstring.func_overload til 0 i din php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 skal mindst være version 2.7.0. Du har version %s installeret.", + "To fix this issue update your libxml2 version and restart your web server." : "Opdater din libxml2 version og genstart webserveren for at løse problemet.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP er tilsyneladende sat op til at fjerne indlejrede doc-blokke. Dette vil gøre adskillige kerneprogrammer utilgængelige.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dette er sansynligvis forårsaget af et accelerator eller cache som Zend OPcache eller eAccelerator", + "PHP modules have been installed, but they are still listed as missing?" : "Der er installeret PHP-moduler, men de fremstår stadig som fraværende?", + "Please ask your server administrator to restart the web server." : "Du bedes anmode din serveradministrator om at genstarte webserveren.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 kræves", + "Please upgrade your database version" : "Opgradér venligst din databaseversion", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Tilpas venligst rettigheder til 0770, så mappen ikke fremvises for andre brugere.", + "Check the value of \"datadirectory\" in your configuration" : "Tjek værdien for \"databibliotek\" i din konfiguration", + "Could not obtain lock type %d on \"%s\"." : "Kunne ikke opnå en låsetype %d på \"%s\".", + "Storage is temporarily not available" : "Lagerplads er midlertidigt ikke tilgængeligt", + "Following databases are supported: %s" : "Følgende databaser understøttes: %s", + "Following platforms are supported: %s" : "Følgende platforme understøttes: %s", + "Overview" : "Overblik", + "Basic settings" : "Grundlæggende Indstillinger", + "Sharing" : "Deling", + "Security" : "Sikkerhed", + "Personal info" : "Personlige oplysninger", + "Mobile & desktop" : "Mobil & desktop" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/da.json b/docker/overlays/nextcloud/html/lib/l10n/da.json new file mode 100644 index 0000000..01402fa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/da.json @@ -0,0 +1,167 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Kan ikke skrive til mappen \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dette kan som regel ordnes ved at give webserveren skrive adgang til config mappen", + "See %s" : "Se %s", + "Sample configuration detected" : "Eksempel for konfiguration registreret", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Der er registreret at eksempel for konfiguration er blevet kopieret. Dette kan ødelægge din installation og understøttes ikke. Læs venligst dokumentationen før der foretages ændringer i config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "PHP %s or higher is required." : "Der kræves PHP %s eller nyere.", + "PHP with a version lower than %s is required." : "Der kræves PHP %s eller ældre.", + "The command line tool %s could not be found" : "Kommandolinjeværktøjet %s blev ikke fundet", + "The library %s is not available." : "Biblioteket %s er ikke tilgængeligt.", + "Server version %s or higher is required." : "Du skal have server version %s eller nyere.", + "Server version %s or lower is required." : "Du skal have server version %s eller ældre.", + "Authentication" : "Godkendelse", + "Unknown filetype" : "Ukendt filtype", + "Invalid image" : "Ugyldigt billede", + "today" : "i dag", + "tomorrow" : "i morgen", + "yesterday" : "i går", + "_in %n day_::_in %n days_" : ["om %n dag","om %n dage"], + "_%n day ago_::_%n days ago_" : ["%n dag siden","%n dage siden"], + "next month" : "næste måned", + "last month" : "sidste måned", + "_in %n month_::_in %n months_" : ["om %n måned","om %n måneder"], + "_%n month ago_::_%n months ago_" : ["%n måned siden","%n måneder siden"], + "next year" : "næste år", + "last year" : "sidste år", + "_in %n year_::_in %n years_" : ["om %n år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år siden","%n år siden"], + "_in %n hour_::_in %n hours_" : ["om %n time","om %n timer"], + "_%n hour ago_::_%n hours ago_" : ["%n time siden","%n timer siden"], + "_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minutter"], + "_%n minute ago_::_%n minutes ago_" : ["%n minut siden","%n minutter siden"], + "in a few seconds" : "om få sekunder", + "seconds ago" : "sekunder siden", + "File name is a reserved word" : "Filnavnet er et reserveret ord", + "File name contains at least one invalid character" : "Filnavnet indeholder mindst ét ugyldigt tegn", + "File name is too long" : "Filnavnet er for langt", + "Dot files are not allowed" : "Filer med punktummer er ikke tilladt", + "Empty filename is not allowed" : "Tomme filnavne er ikke tilladt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan ikke installeres fordi appinfo filen ikke kan læses.", + "__language_name__" : "Dansk", + "Help" : "Hjælp", + "Apps" : "Apps", + "Settings" : "Indstillinger", + "Log out" : "Log ud", + "Users" : "Brugere", + "Unknown user" : "Ukendt bruger", + "Additional settings" : "Yderligere indstillinger", + "%s enter the database username and name." : "%s indtast brugernavn og navn til databasen.", + "%s enter the database username." : "%s indtast database brugernavnet.", + "%s enter the database name." : "%s indtast database navnet.", + "%s you may not use dots in the database name" : "%s du må ikke bruge punktummer i databasenavnet.", + "Oracle connection could not be established" : "Oracle forbindelsen kunne ikke etableres", + "Oracle username and/or password not valid" : "Oracle brugernavn og/eller kodeord er ikke gyldigt.", + "PostgreSQL username and/or password not valid" : "PostgreSQL brugernavn og/eller kodeord er ikke gyldigt.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X understøttes ikke og %s vil ikke virke optimalt på denne platform. Anvend på eget ansvar!", + "For the best results, please consider using a GNU/Linux server instead." : "For de bedste resultater, overvej venligst at bruge en GNU/Linux-server i stedet.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det ser ud til, at denne %s-instans kører på et 32-bit PHP-miljø, samt at open_basedir er blevet konfigureret gennem php.ini. Dette vil føre til problemer med filer som er større end 4GB, og frarådes kraftigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjern venligst indstillingen for open_basedir inde i din php.ini eller skift til 64-bit PHP.", + "Set an admin username." : "Angiv et admin brugernavn.", + "Set an admin password." : "Angiv et admin kodeord.", + "Can't create or write into the data directory %s" : "Kan ikke oprette eller skrive ind i datamappen %s", + "Invalid Federated Cloud ID" : "Ugyldigt Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delingsbackend'en %s skal implementere grænsefladen OCP\\Share_Backend", + "Sharing backend %s not found" : "Delingsbackend'en %s blev ikke fundet", + "Sharing backend for %s not found" : "Delingsbackend'en for %s blev ikke fundet", + "Open »%s«" : "Åbn »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har ikke tilladelse til at dele %s", + "Expiration date is in the past" : "Udløbsdatoen ligger tilbage i tid", + "Click the button below to open it." : "Klik på knappen nedenunder for at åbne.", + "Could not find category \"%s\"" : "Kunne ikke finde kategorien \"%s\"", + "Sunday" : "Søndag", + "Monday" : "Mandag", + "Tuesday" : "Tirsdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lørdag", + "Sun." : "Søn.", + "Mon." : "Man.", + "Tue." : "Tir.", + "Wed." : ".", + "Thu." : "Tor.", + "Fri." : "Fre.", + "Sat." : "Lør.", + "Su" : "Sø", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lø", + "January" : "Januar", + "February" : "Februar", + "March" : "Marts", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "eb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kun følgende tegn kan indgå i et brugernavn: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Et gyldigt brugernavn skal angives", + "Username contains whitespace at the beginning or at the end" : "Brugernavnet har et mellemrum i starten eller slutningen", + "A valid password must be provided" : "En gyldig adgangskode skal angives", + "The username is already being used" : "Brugernavnet er allerede i brug", + "User disabled" : "Bruger deaktiveret", + "Login canceled by app" : "Login annulleret af app", + "a safe home for all your data" : "et sikkert hjem til alle dine data", + "File is currently busy, please try again later" : "Filen er i øjeblikket optaget - forsøg igen senere", + "Can't read file" : "Kan ikke læse filen", + "Application is not enabled" : "Programmet er ikke aktiveret", + "Authentication error" : "Adgangsfejl", + "Token expired. Please reload page." : "Adgang er udløbet. Genindlæs siden.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ingen database driver (sqlite, mysql eller postgresql) er installeret.", + "Cannot write into \"config\" directory" : "Kan ikke skrive til mappen \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dette kan som regel ordnes ved at give webserveren skrive adgang til config mappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan ikke skrive til mappen \"apps\"", + "Setting locale to %s failed" : "Angivelse af %s for lokalitet mislykkedes", + "Please install one of these locales on your system and restart your webserver." : "Installér venligst én af disse lokaliteter på dit system, og genstart din webserver.", + "PHP module %s not installed." : "PHP-modulet %s er ikke installeret.", + "Please ask your server administrator to install the module." : "Du bedes anmode din serveradministrator om at installere modulet.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-indstillingen \"%s\" er ikke angivet til \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er angivet til \"%s\", i stedet for den forventede værdi \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "For at rette dette problem, angiv\nmbstring.func_overload til 0 i din php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 skal mindst være version 2.7.0. Du har version %s installeret.", + "To fix this issue update your libxml2 version and restart your web server." : "Opdater din libxml2 version og genstart webserveren for at løse problemet.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP er tilsyneladende sat op til at fjerne indlejrede doc-blokke. Dette vil gøre adskillige kerneprogrammer utilgængelige.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dette er sansynligvis forårsaget af et accelerator eller cache som Zend OPcache eller eAccelerator", + "PHP modules have been installed, but they are still listed as missing?" : "Der er installeret PHP-moduler, men de fremstår stadig som fraværende?", + "Please ask your server administrator to restart the web server." : "Du bedes anmode din serveradministrator om at genstarte webserveren.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 kræves", + "Please upgrade your database version" : "Opgradér venligst din databaseversion", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Tilpas venligst rettigheder til 0770, så mappen ikke fremvises for andre brugere.", + "Check the value of \"datadirectory\" in your configuration" : "Tjek værdien for \"databibliotek\" i din konfiguration", + "Could not obtain lock type %d on \"%s\"." : "Kunne ikke opnå en låsetype %d på \"%s\".", + "Storage is temporarily not available" : "Lagerplads er midlertidigt ikke tilgængeligt", + "Following databases are supported: %s" : "Følgende databaser understøttes: %s", + "Following platforms are supported: %s" : "Følgende platforme understøttes: %s", + "Overview" : "Overblik", + "Basic settings" : "Grundlæggende Indstillinger", + "Sharing" : "Deling", + "Security" : "Sikkerhed", + "Personal info" : "Personlige oplysninger", + "Mobile & desktop" : "Mobil & desktop" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/de.js b/docker/overlays/nextcloud/html/lib/l10n/de.js new file mode 100644 index 0000000..9814b47 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/de.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Das Schreiben in das „config“-Verzeichnis ist nicht möglich!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dies kann normalerweise repariert werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Oder wenn Du lieber möchtest, dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true.", + "See %s" : "Siehe %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Oder wenn Du lieber möchtest, dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true. Siehe %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$swurden nicht korrekt ersetzt. Stelle sicher, dass es sich um eine mit dem Server kompatible Version handelt.", + "Sample configuration detected" : "Beispielkonfiguration gefunden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Deine Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "Other activities" : "Andere Aktivitäten", + "%1$s and %2$s" : "%1$s und %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s und %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s und %5$s", + "Education Edition" : "Bildungsausgabe", + "Enterprise bundle" : "Firmen-Paket", + "Groupware bundle" : "Groupware-Paket", + "Hub bundle" : "Hub-Paket", + "Social sharing bundle" : "Paket für das Teilen in sozialen Medien", + "PHP %s or higher is required." : "PHP %s oder höher wird benötigt.", + "PHP with a version lower than %s is required." : "PHP wird in einer früheren Version als %s benötigt.", + "%sbit or higher PHP required." : "%sbit oder höheres PHP wird benötigt.", + "The following architectures are supported: %s" : "Die folgenden Architekturen werden unterstützt: %s", + "The following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "The command line tool %s could not be found" : "Das Kommandozeilenwerkzeug %s konnte nicht gefunden werden", + "The library %s is not available." : "Die Bibliothek %s ist nicht verfügbar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer neueren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer früheren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "The following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Server version %s or higher is required." : "Server Version %s oder höher wird benötigt.", + "Server version %s or lower is required." : "Server Version %s oder niedriger wird benötigt.", + "Logged in user must be an admin or sub admin" : "Der angemeldete Benutzer muss ein (Sub-)Administrator sein", + "Logged in user must be an admin" : "Der angemeldete Benutzer muss ein Administrator sein", + "Wiping of device %s has started" : "Löschen von Gerät %s wurde gestartet", + "Wiping of device »%s« has started" : "Löschen von Gerät »%s« wurde gestartet", + "»%s« started remote wipe" : "»%s« hat das Löschen aus der Ferne gestartet", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne gestartet. Du bekommst eine weitere E-Mail sobald der Vorgang beendet wurde", + "Wiping of device %s has finished" : "Löschen von Gerät %s wurde beendet", + "Wiping of device »%s« has finished" : "Löschen von Gerät »%s« wurde beendet", + "»%s« finished remote wipe" : "»%s« hat das Löschen aus der Ferne beendet", + "Device or application »%s« has finished the remote wipe process." : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne beendet.", + "Remote wipe started" : "Fernlöschung gestartet", + "A remote wipe was started on device %s" : "Eine Fernlöschung wurde am Gerät %s gestartet", + "Remote wipe finished" : "Fernlöschung fertig", + "The remote wipe on %s has finished" : "Die Fernlöschung auf %s ist fertig", + "Authentication" : "Authentifizierung", + "Unknown filetype" : "Unbekannter Dateityp", + "Invalid image" : "Ungültiges Bild", + "Avatar image is not square" : "Benutzerbild ist nicht quadratisch", + "today" : "Heute", + "tomorrow" : "Morgen", + "yesterday" : "Gestern", + "_in %n day_::_in %n days_" : ["in %n Tag","in %n Tagen"], + "_%n day ago_::_%n days ago_" : ["Vor %n Tag","Vor %n Tagen"], + "next month" : "Nächsten Monat", + "last month" : "Letzten Monat", + "_in %n month_::_in %n months_" : ["in %n Monat","in %n Monaten"], + "_%n month ago_::_%n months ago_" : ["Vor %n Monat","Vor %n Monaten"], + "next year" : "nächstes Jahr", + "last year" : "Letztes Jahr", + "_in %n year_::_in %n years_" : ["in %n Jahr","in %n Jahren"], + "_%n year ago_::_%n years ago_" : ["Vor %n Jahr","Vor %n Jahren"], + "_in %n hour_::_in %n hours_" : ["in %n Stunde","in %n Stunden"], + "_%n hour ago_::_%n hours ago_" : ["Vor %n Stunde","Vor %n Stunden"], + "_in %n minute_::_in %n minutes_" : ["in %n Minute","in %n Minuten"], + "_%n minute ago_::_%n minutes ago_" : ["Vor %n Minute","Vor %n Minuten"], + "in a few seconds" : "in wenigen Sekunden", + "seconds ago" : "Gerade eben", + "Empty file" : "Leere Datei", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Das Modul mit der ID: %s existiert nicht. Bitte die App in den App-Einstellungen aktivieren oder den Administrator kontaktieren.", + "File name is a reserved word" : "Der Dateiname ist ein reserviertes Wort", + "File name contains at least one invalid character" : "Der Dateiname enthält mindestens ein ungültiges Zeichen", + "File name is too long" : "Dateiname ist zu lang", + "Dot files are not allowed" : "Dateinamen mit einem Punkt am Anfang sind nicht erlaubt", + "Empty filename is not allowed" : "Ein leerer Dateiname ist nicht erlaubt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Die Anwendung \"%s\" kann nicht installiert werden, weil die Anwendungsinfodatei nicht gelesen werden kann.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Die App \"%s\" kann nicht installiert werden, da sie mit dieser Serverversion nicht kompatibel ist.", + "__language_name__" : "Deutsch (Persönlich: Du)", + "This is an automatically sent email, please do not reply." : "Dies ist eine automatisch versandte E-Mail, bitte nicht antworten.", + "Help" : "Hilfe", + "Apps" : "Apps", + "Settings" : "Einstellungen", + "Log out" : "Abmelden", + "Users" : "Benutzer", + "Unknown user" : "Unbekannter Benutzer", + "Additional settings" : "Zusätzliche Einstellungen", + "%s enter the database username and name." : "%s gebe den Datenbank-Benutzernamen und den Datenbanknamen ein.", + "%s enter the database username." : "%s gebe den Datenbank-Benutzernamen an.", + "%s enter the database name." : "%s gebe den Datenbanknamen an.", + "%s you may not use dots in the database name" : "%s Der Datenbankname darf keine Punkte enthalten", + "MySQL username and/or password not valid" : "MySQL-Benutzername und/oder Passwort ungültig", + "You need to enter details of an existing account." : "Du musst Details von einem existierenden Benutzer einfügen.", + "Oracle connection could not be established" : "Es konnte keine Verbindung zur Oracle-Datenbank hergestellt werden", + "Oracle username and/or password not valid" : "Oracle-Benutzername und/oder -Passwort ungültig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-Benutzername und/oder -Passwort ungültig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X wird nicht unterstützt und %s wird auf dieser Plattform nicht richtig funktionieren. Die Benutzung erfolgt auf eigene Gefahr!", + "For the best results, please consider using a GNU/Linux server instead." : "Zur Gewährleistung eines optimalen Betriebs sollte stattdessen ein GNU/Linux-Server verwendet werden.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Es scheint, dass diese %s-Instanz unter einer 32-Bit-PHP-Umgebung läuft und open_basedir in der Datei php.ini konfiguriert worden ist. Von einem solchen Betrieb wird dringend abgeraten, weil es dabei zu Problemen mit Dateien kommt, deren Größe 4 GB übersteigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bitte entferne die open_basedir-Einstellung in Deiner php.ini oder wechsele zu 64-Bit-PHP.", + "Set an admin username." : "Einen Administrator-Benutzernamen setzen.", + "Set an admin password." : "Ein Administrator-Passwort setzen.", + "Can't create or write into the data directory %s" : "Das Datenverzeichnis %s kann nicht erstellt oder es kann darin nicht geschrieben werden.", + "Invalid Federated Cloud ID" : "Ungültige Federated-Cloud-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Freigabe-Backend %s muss in der OCP\\Share_Backend - Schnittstelle implementiert werden", + "Sharing backend %s not found" : "Freigabe-Backend %s nicht gefunden", + "Sharing backend for %s not found" : "Freigabe-Backend für %s nicht gefunden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$shat » %2$s«  mit Dir geteilt und möchte folgendes hinzufügen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$shat »%2$s« mit Dir geteilt und möchte folgendes hinzufügen", + "»%s« added a note to a file shared with you" : "»%s« hat eine Bemerkung zu einer mit Dir geteilten Datei hinzugefügt", + "Open »%s«" : "»%s« öffnen", + "%1$s via %2$s" : "%1$s über %2$s", + "You are not allowed to share %s" : "Die Freigabe von %s ist Dir nicht erlaubt", + "Can’t increase permissions of %s" : "Kann die Berechtigungen von %s nicht erhöhen", + "Files can’t be shared with delete permissions" : "Dateien mit Lösch-Berechtigungen können nicht geteilt werden", + "Files can’t be shared with create permissions" : "Dateien mit Erstell-Berechtigungen können nicht geteilt werden", + "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.", + "Can’t set expiration date more than %s days in the future" : "Das Ablaufdatum kann nicht mehr als %s Tage in der Zukunft liegen", + "%1$s shared »%2$s« with you" : "%1$s hat »%2$s« mit Dir geteilt", + "%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit Dir geteilt.", + "Click the button below to open it." : "Klicke zum Öffnen auf die untere Schaltfläche.", + "The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr", + "Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden", + "Sunday" : "Sonntag", + "Monday" : "Montag", + "Tuesday" : "Dienstag", + "Wednesday" : "Mittwoch", + "Thursday" : "Donnerstag", + "Friday" : "Freitag", + "Saturday" : "Samstag", + "Sun." : "Son.", + "Mon." : "Mon.", + "Tue." : "Die.", + "Wed." : "Mit.", + "Thu." : "Don.", + "Fri." : "Fre.", + "Sat." : "Sam.", + "Su" : "So", + "Mo" : "Mo", + "Tu" : "Di", + "We" : "Mi", + "Th" : "Do", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "Januar", + "February" : "Februar", + "March" : "März", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mär.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“", + "A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden", + "Username contains whitespace at the beginning or at the end" : "Der Benutzername enthält Leerzeichen am Anfang oder am Ende", + "Username must not consist of dots only" : "Der Benutzername darf nicht nur aus Punkten bestehen", + "Username is invalid because files already exist for this user" : "Der Benutzer ist ungültig, da bereits Dateien von diesem Benutzer existieren", + "A valid password must be provided" : "Es muss ein gültiges Passwort eingegeben werden", + "The username is already being used" : "Dieser Benutzername existiert bereits", + "Could not create user" : "Benutzer konnte nicht erstellt werden", + "User disabled" : "Benutzer deaktiviert", + "Login canceled by app" : "Anmeldung durch die App abgebrochen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Die App „%1$s“ kann nicht installiert werden, da die folgenden Abhängigkeiten nicht erfüllt sind: %2$s", + "a safe home for all your data" : "ein sicherer Ort für all Deine Daten", + "File is currently busy, please try again later" : "Die Datei ist in Benutzung, bitte versuche es später noch einmal", + "Can't read file" : "Datei kann nicht gelesen werden", + "Application is not enabled" : "Die Anwendung ist nicht aktiviert", + "Authentication error" : "Authentifizierungsfehler", + "Token expired. Please reload page." : "Token abgelaufen. Bitte lade die Seite neu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Keine Datenbanktreiber (SQLite, MySQL oder PostgreSQL) installiert.", + "Cannot write into \"config\" directory" : "Schreiben in das „config“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Konfigurationsverzeichnis eingeräumt wird. Siehe auch %s", + "Cannot write into \"apps\" directory" : "Schreiben in das „apps“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das App-Verzeichnis gegeben wird oder der App Store in der Konfigurationsdatei deaktiviert wird.", + "Cannot create \"data\" directory" : "Kann das \"Daten\"-Verzeichnis nicht erstellen", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Berechtigungen können zumeist korrigiert werden indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s.", + "Setting locale to %s failed" : "Das Setzen der Umgebungslokale auf %s ist fehlgeschlagen", + "Please install one of these locales on your system and restart your webserver." : "Bitte installiere eine dieser Sprachen auf Deinem System und starte den Webserver neu.", + "PHP module %s not installed." : "PHP-Modul %s nicht installiert.", + "Please ask your server administrator to install the module." : "Bitte für die Installation des Moduls Deinen Server-Administrator kontaktieren.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-Einstellung „%s“ ist nicht auf „%s“ gesetzt.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Eine Änderung dieser Einstellung in der php.ini kann Deine Nextcloud wieder lauffähig machen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ist nicht auf den erwarteten Wert „0“, sondern stattdessen auf „%s“ gesetzt", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bitte setze zum Beheben dieses Problems mbstring.func_overload in Deiner php.ini auf 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.", + "To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, musst Du die libxml2 Version aktualisieren und den Webserver neustarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ist offenbar so konfiguriert, dass PHPDoc-Blöcke in der Anweisung entfernt werden. Dadurch sind mehrere Kern-Apps nicht erreichbar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dies wird wahrscheinlich durch Zwischenspeicher/Beschleuniger wie etwa Zend OPcache oder eAccelerator verursacht.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-Module wurden installiert, werden aber als noch fehlend gelistet?", + "Please ask your server administrator to restart the web server." : "Bitte kontaktiere Deinen Server-Administrator und bitte um den Neustart des Webservers.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 benötigt", + "Please upgrade your database version" : "Bitte aktualisiere Deine Datenbankversion", + "Your data directory is readable by other users" : "Dein Datenverzeichnis kann von anderen Benutzern gelesen werden", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bitte ändere die Berechtigungen auf 0770, sodass das Verzeichnis nicht von anderen Benutzern angezeigt werden kann.", + "Your data directory must be an absolute path" : "Dein Datenverzeichnis muss einen eindeutigen Pfad haben", + "Check the value of \"datadirectory\" in your configuration" : "Überprüfe bitte die Angabe unter „datadirectory“ in Deiner Konfiguration", + "Your data directory is invalid" : "Dein Datenverzeichnis ist ungültig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Stelle sicher, dass eine Datei \".ocdata\" im Wurzelverzeichnis des data-Verzeichnisses existiert.", + "Action \"%s\" not supported or implemented." : "Aktion \"%s\" wird nicht unterstützt oder ist nicht implementiert.", + "Authentication failed, wrong token or provider ID given" : "Authentifizierung ist fehlgeschlagen. Falsches Token oder falsche Provider-ID wurde übertragen.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Es fehlen Parameter, um die Anfrage zu bearbeiten. Fehlende Parameter: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wird bereits von Cloud-Federation-Provider \"%2$s\" verwendet", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud-Federation-Provider mit ID: \"%s\" ist nicht vorhanden.", + "Could not obtain lock type %d on \"%s\"." : "Sperrtyp %d auf „%s“ konnte nicht ermittelt werden.", + "Storage unauthorized. %s" : "Speicher nicht autorisiert. %s", + "Storage incomplete configuration. %s" : "Speicher-Konfiguration unvollständig. %s", + "Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s", + "Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar", + "Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s", + "Following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "Following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Overview" : "Übersicht", + "Basic settings" : "Grundeinstellungen", + "Sharing" : "Teilen", + "Security" : "Sicherheit", + "Groupware" : "Groupware", + "Personal info" : "Persönliche Informationen ", + "Mobile & desktop" : "Mobil & Desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das App-Verzeichnis eingeräumt wird. Siehe auch %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/de.json b/docker/overlays/nextcloud/html/lib/l10n/de.json new file mode 100644 index 0000000..6c786fc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/de.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Das Schreiben in das „config“-Verzeichnis ist nicht möglich!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dies kann normalerweise repariert werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Oder wenn Du lieber möchtest, dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true.", + "See %s" : "Siehe %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Oder wenn Du lieber möchtest, dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true. Siehe %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$swurden nicht korrekt ersetzt. Stelle sicher, dass es sich um eine mit dem Server kompatible Version handelt.", + "Sample configuration detected" : "Beispielkonfiguration gefunden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Deine Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "Other activities" : "Andere Aktivitäten", + "%1$s and %2$s" : "%1$s und %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s und %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s und %5$s", + "Education Edition" : "Bildungsausgabe", + "Enterprise bundle" : "Firmen-Paket", + "Groupware bundle" : "Groupware-Paket", + "Hub bundle" : "Hub-Paket", + "Social sharing bundle" : "Paket für das Teilen in sozialen Medien", + "PHP %s or higher is required." : "PHP %s oder höher wird benötigt.", + "PHP with a version lower than %s is required." : "PHP wird in einer früheren Version als %s benötigt.", + "%sbit or higher PHP required." : "%sbit oder höheres PHP wird benötigt.", + "The following architectures are supported: %s" : "Die folgenden Architekturen werden unterstützt: %s", + "The following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "The command line tool %s could not be found" : "Das Kommandozeilenwerkzeug %s konnte nicht gefunden werden", + "The library %s is not available." : "Die Bibliothek %s ist nicht verfügbar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer neueren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer früheren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "The following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Server version %s or higher is required." : "Server Version %s oder höher wird benötigt.", + "Server version %s or lower is required." : "Server Version %s oder niedriger wird benötigt.", + "Logged in user must be an admin or sub admin" : "Der angemeldete Benutzer muss ein (Sub-)Administrator sein", + "Logged in user must be an admin" : "Der angemeldete Benutzer muss ein Administrator sein", + "Wiping of device %s has started" : "Löschen von Gerät %s wurde gestartet", + "Wiping of device »%s« has started" : "Löschen von Gerät »%s« wurde gestartet", + "»%s« started remote wipe" : "»%s« hat das Löschen aus der Ferne gestartet", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne gestartet. Du bekommst eine weitere E-Mail sobald der Vorgang beendet wurde", + "Wiping of device %s has finished" : "Löschen von Gerät %s wurde beendet", + "Wiping of device »%s« has finished" : "Löschen von Gerät »%s« wurde beendet", + "»%s« finished remote wipe" : "»%s« hat das Löschen aus der Ferne beendet", + "Device or application »%s« has finished the remote wipe process." : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne beendet.", + "Remote wipe started" : "Fernlöschung gestartet", + "A remote wipe was started on device %s" : "Eine Fernlöschung wurde am Gerät %s gestartet", + "Remote wipe finished" : "Fernlöschung fertig", + "The remote wipe on %s has finished" : "Die Fernlöschung auf %s ist fertig", + "Authentication" : "Authentifizierung", + "Unknown filetype" : "Unbekannter Dateityp", + "Invalid image" : "Ungültiges Bild", + "Avatar image is not square" : "Benutzerbild ist nicht quadratisch", + "today" : "Heute", + "tomorrow" : "Morgen", + "yesterday" : "Gestern", + "_in %n day_::_in %n days_" : ["in %n Tag","in %n Tagen"], + "_%n day ago_::_%n days ago_" : ["Vor %n Tag","Vor %n Tagen"], + "next month" : "Nächsten Monat", + "last month" : "Letzten Monat", + "_in %n month_::_in %n months_" : ["in %n Monat","in %n Monaten"], + "_%n month ago_::_%n months ago_" : ["Vor %n Monat","Vor %n Monaten"], + "next year" : "nächstes Jahr", + "last year" : "Letztes Jahr", + "_in %n year_::_in %n years_" : ["in %n Jahr","in %n Jahren"], + "_%n year ago_::_%n years ago_" : ["Vor %n Jahr","Vor %n Jahren"], + "_in %n hour_::_in %n hours_" : ["in %n Stunde","in %n Stunden"], + "_%n hour ago_::_%n hours ago_" : ["Vor %n Stunde","Vor %n Stunden"], + "_in %n minute_::_in %n minutes_" : ["in %n Minute","in %n Minuten"], + "_%n minute ago_::_%n minutes ago_" : ["Vor %n Minute","Vor %n Minuten"], + "in a few seconds" : "in wenigen Sekunden", + "seconds ago" : "Gerade eben", + "Empty file" : "Leere Datei", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Das Modul mit der ID: %s existiert nicht. Bitte die App in den App-Einstellungen aktivieren oder den Administrator kontaktieren.", + "File name is a reserved word" : "Der Dateiname ist ein reserviertes Wort", + "File name contains at least one invalid character" : "Der Dateiname enthält mindestens ein ungültiges Zeichen", + "File name is too long" : "Dateiname ist zu lang", + "Dot files are not allowed" : "Dateinamen mit einem Punkt am Anfang sind nicht erlaubt", + "Empty filename is not allowed" : "Ein leerer Dateiname ist nicht erlaubt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Die Anwendung \"%s\" kann nicht installiert werden, weil die Anwendungsinfodatei nicht gelesen werden kann.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Die App \"%s\" kann nicht installiert werden, da sie mit dieser Serverversion nicht kompatibel ist.", + "__language_name__" : "Deutsch (Persönlich: Du)", + "This is an automatically sent email, please do not reply." : "Dies ist eine automatisch versandte E-Mail, bitte nicht antworten.", + "Help" : "Hilfe", + "Apps" : "Apps", + "Settings" : "Einstellungen", + "Log out" : "Abmelden", + "Users" : "Benutzer", + "Unknown user" : "Unbekannter Benutzer", + "Additional settings" : "Zusätzliche Einstellungen", + "%s enter the database username and name." : "%s gebe den Datenbank-Benutzernamen und den Datenbanknamen ein.", + "%s enter the database username." : "%s gebe den Datenbank-Benutzernamen an.", + "%s enter the database name." : "%s gebe den Datenbanknamen an.", + "%s you may not use dots in the database name" : "%s Der Datenbankname darf keine Punkte enthalten", + "MySQL username and/or password not valid" : "MySQL-Benutzername und/oder Passwort ungültig", + "You need to enter details of an existing account." : "Du musst Details von einem existierenden Benutzer einfügen.", + "Oracle connection could not be established" : "Es konnte keine Verbindung zur Oracle-Datenbank hergestellt werden", + "Oracle username and/or password not valid" : "Oracle-Benutzername und/oder -Passwort ungültig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-Benutzername und/oder -Passwort ungültig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X wird nicht unterstützt und %s wird auf dieser Plattform nicht richtig funktionieren. Die Benutzung erfolgt auf eigene Gefahr!", + "For the best results, please consider using a GNU/Linux server instead." : "Zur Gewährleistung eines optimalen Betriebs sollte stattdessen ein GNU/Linux-Server verwendet werden.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Es scheint, dass diese %s-Instanz unter einer 32-Bit-PHP-Umgebung läuft und open_basedir in der Datei php.ini konfiguriert worden ist. Von einem solchen Betrieb wird dringend abgeraten, weil es dabei zu Problemen mit Dateien kommt, deren Größe 4 GB übersteigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bitte entferne die open_basedir-Einstellung in Deiner php.ini oder wechsele zu 64-Bit-PHP.", + "Set an admin username." : "Einen Administrator-Benutzernamen setzen.", + "Set an admin password." : "Ein Administrator-Passwort setzen.", + "Can't create or write into the data directory %s" : "Das Datenverzeichnis %s kann nicht erstellt oder es kann darin nicht geschrieben werden.", + "Invalid Federated Cloud ID" : "Ungültige Federated-Cloud-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Freigabe-Backend %s muss in der OCP\\Share_Backend - Schnittstelle implementiert werden", + "Sharing backend %s not found" : "Freigabe-Backend %s nicht gefunden", + "Sharing backend for %s not found" : "Freigabe-Backend für %s nicht gefunden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$shat » %2$s«  mit Dir geteilt und möchte folgendes hinzufügen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$shat »%2$s« mit Dir geteilt und möchte folgendes hinzufügen", + "»%s« added a note to a file shared with you" : "»%s« hat eine Bemerkung zu einer mit Dir geteilten Datei hinzugefügt", + "Open »%s«" : "»%s« öffnen", + "%1$s via %2$s" : "%1$s über %2$s", + "You are not allowed to share %s" : "Die Freigabe von %s ist Dir nicht erlaubt", + "Can’t increase permissions of %s" : "Kann die Berechtigungen von %s nicht erhöhen", + "Files can’t be shared with delete permissions" : "Dateien mit Lösch-Berechtigungen können nicht geteilt werden", + "Files can’t be shared with create permissions" : "Dateien mit Erstell-Berechtigungen können nicht geteilt werden", + "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.", + "Can’t set expiration date more than %s days in the future" : "Das Ablaufdatum kann nicht mehr als %s Tage in der Zukunft liegen", + "%1$s shared »%2$s« with you" : "%1$s hat »%2$s« mit Dir geteilt", + "%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit Dir geteilt.", + "Click the button below to open it." : "Klicke zum Öffnen auf die untere Schaltfläche.", + "The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr", + "Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden", + "Sunday" : "Sonntag", + "Monday" : "Montag", + "Tuesday" : "Dienstag", + "Wednesday" : "Mittwoch", + "Thursday" : "Donnerstag", + "Friday" : "Freitag", + "Saturday" : "Samstag", + "Sun." : "Son.", + "Mon." : "Mon.", + "Tue." : "Die.", + "Wed." : "Mit.", + "Thu." : "Don.", + "Fri." : "Fre.", + "Sat." : "Sam.", + "Su" : "So", + "Mo" : "Mo", + "Tu" : "Di", + "We" : "Mi", + "Th" : "Do", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "Januar", + "February" : "Februar", + "March" : "März", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mär.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“", + "A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden", + "Username contains whitespace at the beginning or at the end" : "Der Benutzername enthält Leerzeichen am Anfang oder am Ende", + "Username must not consist of dots only" : "Der Benutzername darf nicht nur aus Punkten bestehen", + "Username is invalid because files already exist for this user" : "Der Benutzer ist ungültig, da bereits Dateien von diesem Benutzer existieren", + "A valid password must be provided" : "Es muss ein gültiges Passwort eingegeben werden", + "The username is already being used" : "Dieser Benutzername existiert bereits", + "Could not create user" : "Benutzer konnte nicht erstellt werden", + "User disabled" : "Benutzer deaktiviert", + "Login canceled by app" : "Anmeldung durch die App abgebrochen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Die App „%1$s“ kann nicht installiert werden, da die folgenden Abhängigkeiten nicht erfüllt sind: %2$s", + "a safe home for all your data" : "ein sicherer Ort für all Deine Daten", + "File is currently busy, please try again later" : "Die Datei ist in Benutzung, bitte versuche es später noch einmal", + "Can't read file" : "Datei kann nicht gelesen werden", + "Application is not enabled" : "Die Anwendung ist nicht aktiviert", + "Authentication error" : "Authentifizierungsfehler", + "Token expired. Please reload page." : "Token abgelaufen. Bitte lade die Seite neu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Keine Datenbanktreiber (SQLite, MySQL oder PostgreSQL) installiert.", + "Cannot write into \"config\" directory" : "Schreiben in das „config“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Konfigurationsverzeichnis eingeräumt wird. Siehe auch %s", + "Cannot write into \"apps\" directory" : "Schreiben in das „apps“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das App-Verzeichnis gegeben wird oder der App Store in der Konfigurationsdatei deaktiviert wird.", + "Cannot create \"data\" directory" : "Kann das \"Daten\"-Verzeichnis nicht erstellen", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Berechtigungen können zumeist korrigiert werden indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s.", + "Setting locale to %s failed" : "Das Setzen der Umgebungslokale auf %s ist fehlgeschlagen", + "Please install one of these locales on your system and restart your webserver." : "Bitte installiere eine dieser Sprachen auf Deinem System und starte den Webserver neu.", + "PHP module %s not installed." : "PHP-Modul %s nicht installiert.", + "Please ask your server administrator to install the module." : "Bitte für die Installation des Moduls Deinen Server-Administrator kontaktieren.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-Einstellung „%s“ ist nicht auf „%s“ gesetzt.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Eine Änderung dieser Einstellung in der php.ini kann Deine Nextcloud wieder lauffähig machen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ist nicht auf den erwarteten Wert „0“, sondern stattdessen auf „%s“ gesetzt", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bitte setze zum Beheben dieses Problems mbstring.func_overload in Deiner php.ini auf 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.", + "To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, musst Du die libxml2 Version aktualisieren und den Webserver neustarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ist offenbar so konfiguriert, dass PHPDoc-Blöcke in der Anweisung entfernt werden. Dadurch sind mehrere Kern-Apps nicht erreichbar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dies wird wahrscheinlich durch Zwischenspeicher/Beschleuniger wie etwa Zend OPcache oder eAccelerator verursacht.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-Module wurden installiert, werden aber als noch fehlend gelistet?", + "Please ask your server administrator to restart the web server." : "Bitte kontaktiere Deinen Server-Administrator und bitte um den Neustart des Webservers.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 benötigt", + "Please upgrade your database version" : "Bitte aktualisiere Deine Datenbankversion", + "Your data directory is readable by other users" : "Dein Datenverzeichnis kann von anderen Benutzern gelesen werden", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bitte ändere die Berechtigungen auf 0770, sodass das Verzeichnis nicht von anderen Benutzern angezeigt werden kann.", + "Your data directory must be an absolute path" : "Dein Datenverzeichnis muss einen eindeutigen Pfad haben", + "Check the value of \"datadirectory\" in your configuration" : "Überprüfe bitte die Angabe unter „datadirectory“ in Deiner Konfiguration", + "Your data directory is invalid" : "Dein Datenverzeichnis ist ungültig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Stelle sicher, dass eine Datei \".ocdata\" im Wurzelverzeichnis des data-Verzeichnisses existiert.", + "Action \"%s\" not supported or implemented." : "Aktion \"%s\" wird nicht unterstützt oder ist nicht implementiert.", + "Authentication failed, wrong token or provider ID given" : "Authentifizierung ist fehlgeschlagen. Falsches Token oder falsche Provider-ID wurde übertragen.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Es fehlen Parameter, um die Anfrage zu bearbeiten. Fehlende Parameter: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wird bereits von Cloud-Federation-Provider \"%2$s\" verwendet", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud-Federation-Provider mit ID: \"%s\" ist nicht vorhanden.", + "Could not obtain lock type %d on \"%s\"." : "Sperrtyp %d auf „%s“ konnte nicht ermittelt werden.", + "Storage unauthorized. %s" : "Speicher nicht autorisiert. %s", + "Storage incomplete configuration. %s" : "Speicher-Konfiguration unvollständig. %s", + "Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s", + "Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar", + "Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s", + "Following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "Following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Overview" : "Übersicht", + "Basic settings" : "Grundeinstellungen", + "Sharing" : "Teilen", + "Security" : "Sicherheit", + "Groupware" : "Groupware", + "Personal info" : "Persönliche Informationen ", + "Mobile & desktop" : "Mobil & Desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das App-Verzeichnis eingeräumt wird. Siehe auch %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/de_DE.js b/docker/overlays/nextcloud/html/lib/l10n/de_DE.js new file mode 100644 index 0000000..fc1d76f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/de_DE.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Das Schreiben in das „config“-Verzeichnis ist nicht möglich!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dies kann normalerweise repariert werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Oder wenn Sie möchten, dass die Datei config.php schreibgeschützt bleiben soll, dann setzen Sie die Option \"config_is_read_only\" in der Datei auf True.", + "See %s" : "Siehe %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Oder wenn Sie möchten, dass die Datei config.php schreibgeschützt bleiben soll, dann setzen Sie die Option \"config_is_read_only\" in der Datei auf True. Siehe %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.", + "Sample configuration detected" : "Beispielkonfiguration gefunden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "Other activities" : "Andere Aktivitäten", + "%1$s and %2$s" : "%1$s und %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s und %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s und %5$s", + "Education Edition" : "Bildungsausgabe", + "Enterprise bundle" : "Firmen-Paket", + "Groupware bundle" : "Groupware-Paket", + "Hub bundle" : "Hub-Paket", + "Social sharing bundle" : "Paket für das Teilen in sozialen Medien", + "PHP %s or higher is required." : "PHP %s oder höher wird benötigt.", + "PHP with a version lower than %s is required." : "PHP wird in einer früheren Version als %s benötigt.", + "%sbit or higher PHP required." : "%sbit oder höheres PHP wird benötigt.", + "The following architectures are supported: %s" : "Folgende Architekturen werden unterstützt: %s", + "The following databases are supported: %s" : "Folgende Datenbanken werden unterstützt: %s", + "The command line tool %s could not be found" : "Das Kommandozeilenwerkzeug %s konnte nicht gefunden werden", + "The library %s is not available." : "Die Bibliothek %s ist nicht verfügbar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer neueren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer früheren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "The following platforms are supported: %s" : "Folgende Plattformen werden unterstützt: %s", + "Server version %s or higher is required." : "Server Version %s oder höher wird benötigt.", + "Server version %s or lower is required." : "Server Version %s oder niedriger wird benötigt.", + "Logged in user must be an admin or sub admin" : "Der angemeldete Benutzer muss ein (Sub-)Administrator sein", + "Logged in user must be an admin" : "Der angemeldete Benutzer muss ein Administrator sein", + "Wiping of device %s has started" : "Löschen von Gerät %s wurde gestartet", + "Wiping of device »%s« has started" : "Löschen von Gerät »%s« wurde gestartet", + "»%s« started remote wipe" : "»%s« hat das Löschen aus der Ferne gestartet", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne gestartet. Sie bekommen eine weitere E-Mail sobald der Vorgang beendet wurde", + "Wiping of device %s has finished" : "Löschen von Gerät %s wurde beendet", + "Wiping of device »%s« has finished" : "Löschen von Gerät »%s« wurde beendet", + "»%s« finished remote wipe" : "»%s« hat das Löschen aus der Ferne beendet", + "Device or application »%s« has finished the remote wipe process." : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne beendet.", + "Remote wipe started" : "Fernlöschung gestartet", + "A remote wipe was started on device %s" : "Eine Fernlöschung wurde am Gerät %s gestartet", + "Remote wipe finished" : "Fernlöschung fertig", + "The remote wipe on %s has finished" : "Die Fernlöschung auf %s ist fertig", + "Authentication" : "Authentifizierung", + "Unknown filetype" : "Unbekannter Dateityp", + "Invalid image" : "Ungültiges Bild", + "Avatar image is not square" : "Benutzerbild ist nicht quadratisch", + "today" : "Heute", + "tomorrow" : "Morgen", + "yesterday" : "Gestern", + "_in %n day_::_in %n days_" : ["in %n Tag","in %n Tagen"], + "_%n day ago_::_%n days ago_" : ["Vor %n Tag","Vor %n Tagen"], + "next month" : "Nächsten Monat", + "last month" : "Letzten Monat", + "_in %n month_::_in %n months_" : ["in %n Monat","in %n Monaten"], + "_%n month ago_::_%n months ago_" : ["Vor %n Monat","Vor %n Monaten"], + "next year" : "nächstes Jahr", + "last year" : "Letztes Jahr", + "_in %n year_::_in %n years_" : ["in %n Jahr","in %n Jahren"], + "_%n year ago_::_%n years ago_" : ["Vor %n Jahr","Vor %n Jahren"], + "_in %n hour_::_in %n hours_" : ["in %n Stunde","in %n Stunden"], + "_%n hour ago_::_%n hours ago_" : ["Vor %n Stunde","Vor %n Stunden"], + "_in %n minute_::_in %n minutes_" : ["in %n Minute","in %n Minuten"], + "_%n minute ago_::_%n minutes ago_" : ["Vor %n Minute","Vor %n Minuten"], + "in a few seconds" : "in wenigen Sekunden", + "seconds ago" : "Gerade eben", + "Empty file" : "Leere Datei", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Das Modul mit der ID: %s existiert nicht. Bitte aktivieren Sie es in Ihren Einstellungen oder kontaktieren Sie Ihren Administrator.", + "File name is a reserved word" : "Der Dateiname ist ein reserviertes Wort", + "File name contains at least one invalid character" : "Der Dateiname enthält mindestens ein ungültiges Zeichen", + "File name is too long" : "Dateiname ist zu lang", + "Dot files are not allowed" : "Dateinamen mit einem Punkt am Anfang sind nicht erlaubt", + "Empty filename is not allowed" : "Ein leerer Dateiname ist nicht erlaubt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Die Anwendung \"%s\" kann nicht installiert werden, weil die Anwendungsinfodatei nicht gelesen werden kann.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Die App \"%s\" kann nicht installiert werden, da sie mit dieser Serverversion nicht kompatibel ist.", + "__language_name__" : "Deutsch (Förmlich: Sie)", + "This is an automatically sent email, please do not reply." : "Dies ist eine automatisch versandte E-Mail, bitte nicht antworten.", + "Help" : "Hilfe", + "Apps" : "Apps", + "Settings" : "Einstellungen", + "Log out" : "Abmelden", + "Users" : "Benutzer", + "Unknown user" : "Unbekannter Benutzer", + "Additional settings" : "Zusätzliche Einstellungen", + "%s enter the database username and name." : "%s geben Sie den Datenbank-Benutzernamen und den Datenbanknamen an.", + "%s enter the database username." : "%s geben Sie den Datenbank-Benutzernamen an.", + "%s enter the database name." : "%s geben Sie den Datenbanknamen an.", + "%s you may not use dots in the database name" : "%s Der Datenbankname darf keine Punkte enthalten", + "MySQL username and/or password not valid" : "MySQL-Benutzername und/oder Passwort ungültig", + "You need to enter details of an existing account." : "Sie müssen Details von einem existierenden Benutzer einfügen.", + "Oracle connection could not be established" : "Es konnte keine Verbindung zur Oracle-Datenbank hergestellt werden", + "Oracle username and/or password not valid" : "Oracle-Benutzername und/oder -Passwort ungültig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-Benutzername und/oder -Passwort ungültig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X wird nicht unterstützt und %s wird auf dieser Plattform nicht richtig funktionieren. Die Benutzung erfolgt auf eigene Gefahr!", + "For the best results, please consider using a GNU/Linux server instead." : "Zur Gewährleistung eines optimalen Betriebs sollte stattdessen ein GNU/Linux-Server verwendet werden.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Es scheint, dass diese %s-Instanz unter einer 32-Bit-PHP-Umgebung läuft und open_basedir in der Datei php.ini konfiguriert worden ist. Von einem solchen Betrieb wird dringend abgeraten, weil es dabei zu Problemen mit Dateien kommt, deren Größe 4 GB übersteigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bitte entfernen Sie die open_basedir-Einstellung in Ihrer php.ini oder wechseln Sie zu 64-Bit-PHP.", + "Set an admin username." : "Einen Administrator-Benutzernamen setzen.", + "Set an admin password." : "Ein Administrator-Passwort setzen.", + "Can't create or write into the data directory %s" : "Das Datenverzeichnis %s kann nicht erstellt oder es kann darin nicht geschrieben werden.", + "Invalid Federated Cloud ID" : "Ungültige Federated-Cloud-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Freigabe-Backend %s muss in der OCP\\Share_Backend - Schnittstelle implementiert werden", + "Sharing backend %s not found" : "Freigabe-Backend %s nicht gefunden", + "Sharing backend for %s not found" : "Freigabe-Backend für %s nicht gefunden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s hat » %2$s« mit Ihnen geteilt und möchte folgendes hinzufügen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s hat »%2$s« mit Ihnen geteilt und möchte folgendes hinzufügen", + "»%s« added a note to a file shared with you" : "»%s« hat eine Bemerkung zu einer mit Ihnen geteilten Datei hinzugefügt", + "Open »%s«" : "»%s« öffnen", + "%1$s via %2$s" : "%1$s über %2$s", + "You are not allowed to share %s" : "Die Freigabe von %s ist Ihnen nicht erlaubt", + "Can’t increase permissions of %s" : "Kann die Berechtigungen von %s nicht erhöhen", + "Files can’t be shared with delete permissions" : "Dateien mit Lösch-Berechtigungen können nicht geteilt werden", + "Files can’t be shared with create permissions" : "Dateien mit Erstell-Berechtigungen können nicht geteilt werden", + "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.", + "Can’t set expiration date more than %s days in the future" : "Das Ablaufdatum kann nicht mehr als %s Tage in der Zukunft liegen", + "%1$s shared »%2$s« with you" : "%1$s hat »%2$s« mit Ihnen geteilt", + "%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit Ihnen geteilt.", + "Click the button below to open it." : "Klicken Sie zum Öffnen auf die untere Schaltfläche.", + "The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr", + "Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden", + "Sunday" : "Sonntag", + "Monday" : "Montag", + "Tuesday" : "Dienstag", + "Wednesday" : "Mittwoch", + "Thursday" : "Donnerstag", + "Friday" : "Freitag", + "Saturday" : "Samstag", + "Sun." : "Son.", + "Mon." : "Mon.", + "Tue." : "Die.", + "Wed." : "Mit.", + "Thu." : "Don.", + "Fri." : "Fre.", + "Sat." : "Sam.", + "Su" : "So", + "Mo" : "Mo", + "Tu" : "Di", + "We" : "Mi", + "Th" : "Do", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "Januar", + "February" : "Februar", + "March" : "März", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mär.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“", + "A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden", + "Username contains whitespace at the beginning or at the end" : "Der Benutzername enthält Leerzeichen am Anfang oder am Ende", + "Username must not consist of dots only" : "Der Benutzername darf nicht nur aus Punkten bestehen", + "Username is invalid because files already exist for this user" : "Der Benutzer ist ungültig, da bereits Dateien von diesem Benutzer existieren", + "A valid password must be provided" : "Es muss ein gültiges Passwort eingegeben werden", + "The username is already being used" : "Dieser Benutzername existiert bereits", + "Could not create user" : "Benutzer konnte nicht erstellt werden", + "User disabled" : "Benutzer deaktiviert", + "Login canceled by app" : "Anmeldung durch die App abgebrochen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Die App „%1$s“ kann nicht installiert werden, da die folgenden Abhängigkeiten nicht erfüllt sind: %2$s", + "a safe home for all your data" : "ein sicherer Ort für all Ihre Daten", + "File is currently busy, please try again later" : "Die Datei ist in Benutzung, bitte versuchen Sie es später noch einmal", + "Can't read file" : "Datei kann nicht gelesen werden", + "Application is not enabled" : "Die Anwendung ist nicht aktiviert", + "Authentication error" : "Authentifizierungsfehler", + "Token expired. Please reload page." : "Token abgelaufen. Bitte laden Sie die Seite neu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Keine Datenbanktreiber (SQLite, MySQL oder PostgreSQL) installiert.", + "Cannot write into \"config\" directory" : "Schreiben in das „config“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Konfigurationsverzeichnis eingeräumt wird. Siehe auch %s", + "Cannot write into \"apps\" directory" : "Schreiben in das „apps“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das App-Verzeichnis gegeben wird oder der App Store in der Konfigurationsdatei deaktiviert wird.", + "Cannot create \"data\" directory" : "Kann das \"Daten\"-Verzeichnis nicht erstellen", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Berechtigungen können zumeist korrigiert werden indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s. ", + "Setting locale to %s failed" : "Das Setzen der Umgebungslokale auf %s ist fehlgeschlagen", + "Please install one of these locales on your system and restart your webserver." : "Bitte installieren Sie eine dieser Sprachen auf Ihrem System und starten Sie den Webserver neu.", + "PHP module %s not installed." : "PHP-Modul %s nicht installiert.", + "Please ask your server administrator to install the module." : "Bitte kontaktieren Sie Ihren Server-Administrator und bitten Sie um die Installation des Moduls.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-Einstellung „%s“ ist nicht auf „%s“ gesetzt.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Eine Änderung dieser Einstellung in der php.ini kann Ihre Nextcloud wieder lauffähig machen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ist nicht auf den erwarteten Wert „0“, sondern stattdessen auf „%s“ gesetzt", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bitte setzen Sie zum Beheben dieses Problems mbstring.func_overload in Ihrer php.ini auf 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.", + "To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, müssen Sie die libxml2 Version aktualisieren und den Webserver neustarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ist offenbar so konfiguriert, dass PHPDoc-Blöcke in der Anweisung entfernt werden. Dadurch sind mehrere Kern-Apps nicht erreichbar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dies wird wahrscheinlich durch Zwischenspeicher/Beschleuniger wie etwa Zend OPcache oder eAccelerator verursacht.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-Module wurden installiert, werden aber als noch fehlend gelistet?", + "Please ask your server administrator to restart the web server." : "Bitte kontaktieren Sie Ihren Server-Administrator und bitten Sie um den Neustart des Webservers.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 benötigt", + "Please upgrade your database version" : "Bitte aktualisieren Sie Ihre Datenbankversion", + "Your data directory is readable by other users" : "Ihr Datenverzeichnis kann von anderen Benutzern gelesen werden", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bitte ändern Sie die Berechtigungen auf 0770, so dass das Verzeichnis nicht von anderen Benutzern angezeigt werden kann.", + "Your data directory must be an absolute path" : "Ihr Datenverzeichnis muss einen eindeutigen Pfad haben", + "Check the value of \"datadirectory\" in your configuration" : "Überprüfen Sie bitte die Angabe unter „datadirectory“ in Ihrer Konfiguration", + "Your data directory is invalid" : "Ihr Datenverzeichnis ist ungültig.", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Stellen Sie sicher, dass eine Datei \".ocdata\" im Wurzelverzeichnis des data-Verzeichnisses existiert.", + "Action \"%s\" not supported or implemented." : "Aktion \"%s\" wird nicht unterstützt oder ist nicht implementiert.", + "Authentication failed, wrong token or provider ID given" : "Authentifizierung ist fehlgeschlagen. Falsches Token oder Provider-ID wurde übertragen.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Es fehlen Parameter um die Anfrage zu bearbeiten. Fehlende Parameter: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wird bereits von Cloud-Federation-Provider \"%2$s\" verwendet.", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud-Federation-Provider mit ID: \"%s\" ist nicht vorhanden.", + "Could not obtain lock type %d on \"%s\"." : "Sperrtyp %d auf „%s“ konnte nicht ermittelt werden.", + "Storage unauthorized. %s" : "Speicher nicht autorisiert. %s", + "Storage incomplete configuration. %s" : "Speicher-Konfiguration unvollständig. %s", + "Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s", + "Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar", + "Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s", + "Following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "Following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Overview" : "Übersicht", + "Basic settings" : "Grundeinstellungen", + "Sharing" : "Teilen", + "Security" : "Sicherheit", + "Groupware" : "Groupware", + "Personal info" : "Persönliche Informationen ", + "Mobile & desktop" : "Mobil & Desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das App-Verzeichnis eingeräumt wird. Siehe auch %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/de_DE.json b/docker/overlays/nextcloud/html/lib/l10n/de_DE.json new file mode 100644 index 0000000..f1c5773 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/de_DE.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Das Schreiben in das „config“-Verzeichnis ist nicht möglich!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dies kann normalerweise repariert werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Oder wenn Sie möchten, dass die Datei config.php schreibgeschützt bleiben soll, dann setzen Sie die Option \"config_is_read_only\" in der Datei auf True.", + "See %s" : "Siehe %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Oder wenn Sie möchten, dass die Datei config.php schreibgeschützt bleiben soll, dann setzen Sie die Option \"config_is_read_only\" in der Datei auf True. Siehe %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.", + "Sample configuration detected" : "Beispielkonfiguration gefunden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "Other activities" : "Andere Aktivitäten", + "%1$s and %2$s" : "%1$s und %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s und %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s und %5$s", + "Education Edition" : "Bildungsausgabe", + "Enterprise bundle" : "Firmen-Paket", + "Groupware bundle" : "Groupware-Paket", + "Hub bundle" : "Hub-Paket", + "Social sharing bundle" : "Paket für das Teilen in sozialen Medien", + "PHP %s or higher is required." : "PHP %s oder höher wird benötigt.", + "PHP with a version lower than %s is required." : "PHP wird in einer früheren Version als %s benötigt.", + "%sbit or higher PHP required." : "%sbit oder höheres PHP wird benötigt.", + "The following architectures are supported: %s" : "Folgende Architekturen werden unterstützt: %s", + "The following databases are supported: %s" : "Folgende Datenbanken werden unterstützt: %s", + "The command line tool %s could not be found" : "Das Kommandozeilenwerkzeug %s konnte nicht gefunden werden", + "The library %s is not available." : "Die Bibliothek %s ist nicht verfügbar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer neueren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Die Bibliothek %1$s wird in einer früheren Version als %2$s benötigt - verfügbare Version ist %3$s.", + "The following platforms are supported: %s" : "Folgende Plattformen werden unterstützt: %s", + "Server version %s or higher is required." : "Server Version %s oder höher wird benötigt.", + "Server version %s or lower is required." : "Server Version %s oder niedriger wird benötigt.", + "Logged in user must be an admin or sub admin" : "Der angemeldete Benutzer muss ein (Sub-)Administrator sein", + "Logged in user must be an admin" : "Der angemeldete Benutzer muss ein Administrator sein", + "Wiping of device %s has started" : "Löschen von Gerät %s wurde gestartet", + "Wiping of device »%s« has started" : "Löschen von Gerät »%s« wurde gestartet", + "»%s« started remote wipe" : "»%s« hat das Löschen aus der Ferne gestartet", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne gestartet. Sie bekommen eine weitere E-Mail sobald der Vorgang beendet wurde", + "Wiping of device %s has finished" : "Löschen von Gerät %s wurde beendet", + "Wiping of device »%s« has finished" : "Löschen von Gerät »%s« wurde beendet", + "»%s« finished remote wipe" : "»%s« hat das Löschen aus der Ferne beendet", + "Device or application »%s« has finished the remote wipe process." : "Gerät oder Anwendung »%s« hat den Vorgang des Löschens aus der Ferne beendet.", + "Remote wipe started" : "Fernlöschung gestartet", + "A remote wipe was started on device %s" : "Eine Fernlöschung wurde am Gerät %s gestartet", + "Remote wipe finished" : "Fernlöschung fertig", + "The remote wipe on %s has finished" : "Die Fernlöschung auf %s ist fertig", + "Authentication" : "Authentifizierung", + "Unknown filetype" : "Unbekannter Dateityp", + "Invalid image" : "Ungültiges Bild", + "Avatar image is not square" : "Benutzerbild ist nicht quadratisch", + "today" : "Heute", + "tomorrow" : "Morgen", + "yesterday" : "Gestern", + "_in %n day_::_in %n days_" : ["in %n Tag","in %n Tagen"], + "_%n day ago_::_%n days ago_" : ["Vor %n Tag","Vor %n Tagen"], + "next month" : "Nächsten Monat", + "last month" : "Letzten Monat", + "_in %n month_::_in %n months_" : ["in %n Monat","in %n Monaten"], + "_%n month ago_::_%n months ago_" : ["Vor %n Monat","Vor %n Monaten"], + "next year" : "nächstes Jahr", + "last year" : "Letztes Jahr", + "_in %n year_::_in %n years_" : ["in %n Jahr","in %n Jahren"], + "_%n year ago_::_%n years ago_" : ["Vor %n Jahr","Vor %n Jahren"], + "_in %n hour_::_in %n hours_" : ["in %n Stunde","in %n Stunden"], + "_%n hour ago_::_%n hours ago_" : ["Vor %n Stunde","Vor %n Stunden"], + "_in %n minute_::_in %n minutes_" : ["in %n Minute","in %n Minuten"], + "_%n minute ago_::_%n minutes ago_" : ["Vor %n Minute","Vor %n Minuten"], + "in a few seconds" : "in wenigen Sekunden", + "seconds ago" : "Gerade eben", + "Empty file" : "Leere Datei", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Das Modul mit der ID: %s existiert nicht. Bitte aktivieren Sie es in Ihren Einstellungen oder kontaktieren Sie Ihren Administrator.", + "File name is a reserved word" : "Der Dateiname ist ein reserviertes Wort", + "File name contains at least one invalid character" : "Der Dateiname enthält mindestens ein ungültiges Zeichen", + "File name is too long" : "Dateiname ist zu lang", + "Dot files are not allowed" : "Dateinamen mit einem Punkt am Anfang sind nicht erlaubt", + "Empty filename is not allowed" : "Ein leerer Dateiname ist nicht erlaubt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Die Anwendung \"%s\" kann nicht installiert werden, weil die Anwendungsinfodatei nicht gelesen werden kann.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Die App \"%s\" kann nicht installiert werden, da sie mit dieser Serverversion nicht kompatibel ist.", + "__language_name__" : "Deutsch (Förmlich: Sie)", + "This is an automatically sent email, please do not reply." : "Dies ist eine automatisch versandte E-Mail, bitte nicht antworten.", + "Help" : "Hilfe", + "Apps" : "Apps", + "Settings" : "Einstellungen", + "Log out" : "Abmelden", + "Users" : "Benutzer", + "Unknown user" : "Unbekannter Benutzer", + "Additional settings" : "Zusätzliche Einstellungen", + "%s enter the database username and name." : "%s geben Sie den Datenbank-Benutzernamen und den Datenbanknamen an.", + "%s enter the database username." : "%s geben Sie den Datenbank-Benutzernamen an.", + "%s enter the database name." : "%s geben Sie den Datenbanknamen an.", + "%s you may not use dots in the database name" : "%s Der Datenbankname darf keine Punkte enthalten", + "MySQL username and/or password not valid" : "MySQL-Benutzername und/oder Passwort ungültig", + "You need to enter details of an existing account." : "Sie müssen Details von einem existierenden Benutzer einfügen.", + "Oracle connection could not be established" : "Es konnte keine Verbindung zur Oracle-Datenbank hergestellt werden", + "Oracle username and/or password not valid" : "Oracle-Benutzername und/oder -Passwort ungültig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-Benutzername und/oder -Passwort ungültig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X wird nicht unterstützt und %s wird auf dieser Plattform nicht richtig funktionieren. Die Benutzung erfolgt auf eigene Gefahr!", + "For the best results, please consider using a GNU/Linux server instead." : "Zur Gewährleistung eines optimalen Betriebs sollte stattdessen ein GNU/Linux-Server verwendet werden.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Es scheint, dass diese %s-Instanz unter einer 32-Bit-PHP-Umgebung läuft und open_basedir in der Datei php.ini konfiguriert worden ist. Von einem solchen Betrieb wird dringend abgeraten, weil es dabei zu Problemen mit Dateien kommt, deren Größe 4 GB übersteigt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bitte entfernen Sie die open_basedir-Einstellung in Ihrer php.ini oder wechseln Sie zu 64-Bit-PHP.", + "Set an admin username." : "Einen Administrator-Benutzernamen setzen.", + "Set an admin password." : "Ein Administrator-Passwort setzen.", + "Can't create or write into the data directory %s" : "Das Datenverzeichnis %s kann nicht erstellt oder es kann darin nicht geschrieben werden.", + "Invalid Federated Cloud ID" : "Ungültige Federated-Cloud-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Freigabe-Backend %s muss in der OCP\\Share_Backend - Schnittstelle implementiert werden", + "Sharing backend %s not found" : "Freigabe-Backend %s nicht gefunden", + "Sharing backend for %s not found" : "Freigabe-Backend für %s nicht gefunden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s hat » %2$s« mit Ihnen geteilt und möchte folgendes hinzufügen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s hat »%2$s« mit Ihnen geteilt und möchte folgendes hinzufügen", + "»%s« added a note to a file shared with you" : "»%s« hat eine Bemerkung zu einer mit Ihnen geteilten Datei hinzugefügt", + "Open »%s«" : "»%s« öffnen", + "%1$s via %2$s" : "%1$s über %2$s", + "You are not allowed to share %s" : "Die Freigabe von %s ist Ihnen nicht erlaubt", + "Can’t increase permissions of %s" : "Kann die Berechtigungen von %s nicht erhöhen", + "Files can’t be shared with delete permissions" : "Dateien mit Lösch-Berechtigungen können nicht geteilt werden", + "Files can’t be shared with create permissions" : "Dateien mit Erstell-Berechtigungen können nicht geteilt werden", + "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.", + "Can’t set expiration date more than %s days in the future" : "Das Ablaufdatum kann nicht mehr als %s Tage in der Zukunft liegen", + "%1$s shared »%2$s« with you" : "%1$s hat »%2$s« mit Ihnen geteilt", + "%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit Ihnen geteilt.", + "Click the button below to open it." : "Klicken Sie zum Öffnen auf die untere Schaltfläche.", + "The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr", + "Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden", + "Sunday" : "Sonntag", + "Monday" : "Montag", + "Tuesday" : "Dienstag", + "Wednesday" : "Mittwoch", + "Thursday" : "Donnerstag", + "Friday" : "Freitag", + "Saturday" : "Samstag", + "Sun." : "Son.", + "Mon." : "Mon.", + "Tue." : "Die.", + "Wed." : "Mit.", + "Thu." : "Don.", + "Fri." : "Fre.", + "Sat." : "Sam.", + "Su" : "So", + "Mo" : "Mo", + "Tu" : "Di", + "We" : "Mi", + "Th" : "Do", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "Januar", + "February" : "Februar", + "March" : "März", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mär.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“", + "A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden", + "Username contains whitespace at the beginning or at the end" : "Der Benutzername enthält Leerzeichen am Anfang oder am Ende", + "Username must not consist of dots only" : "Der Benutzername darf nicht nur aus Punkten bestehen", + "Username is invalid because files already exist for this user" : "Der Benutzer ist ungültig, da bereits Dateien von diesem Benutzer existieren", + "A valid password must be provided" : "Es muss ein gültiges Passwort eingegeben werden", + "The username is already being used" : "Dieser Benutzername existiert bereits", + "Could not create user" : "Benutzer konnte nicht erstellt werden", + "User disabled" : "Benutzer deaktiviert", + "Login canceled by app" : "Anmeldung durch die App abgebrochen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Die App „%1$s“ kann nicht installiert werden, da die folgenden Abhängigkeiten nicht erfüllt sind: %2$s", + "a safe home for all your data" : "ein sicherer Ort für all Ihre Daten", + "File is currently busy, please try again later" : "Die Datei ist in Benutzung, bitte versuchen Sie es später noch einmal", + "Can't read file" : "Datei kann nicht gelesen werden", + "Application is not enabled" : "Die Anwendung ist nicht aktiviert", + "Authentication error" : "Authentifizierungsfehler", + "Token expired. Please reload page." : "Token abgelaufen. Bitte laden Sie die Seite neu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Keine Datenbanktreiber (SQLite, MySQL oder PostgreSQL) installiert.", + "Cannot write into \"config\" directory" : "Schreiben in das „config“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Konfigurationsverzeichnis eingeräumt wird. Siehe auch %s", + "Cannot write into \"apps\" directory" : "Schreiben in das „apps“-Verzeichnis ist nicht möglich", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das App-Verzeichnis gegeben wird oder der App Store in der Konfigurationsdatei deaktiviert wird.", + "Cannot create \"data\" directory" : "Kann das \"Daten\"-Verzeichnis nicht erstellen", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Berechtigungen können zumeist korrigiert werden indem dem Web-Server Schreibzugriff auf das Wurzel-Verzeichnis eingeräumt wird. Siehe auch %s. ", + "Setting locale to %s failed" : "Das Setzen der Umgebungslokale auf %s ist fehlgeschlagen", + "Please install one of these locales on your system and restart your webserver." : "Bitte installieren Sie eine dieser Sprachen auf Ihrem System und starten Sie den Webserver neu.", + "PHP module %s not installed." : "PHP-Modul %s nicht installiert.", + "Please ask your server administrator to install the module." : "Bitte kontaktieren Sie Ihren Server-Administrator und bitten Sie um die Installation des Moduls.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-Einstellung „%s“ ist nicht auf „%s“ gesetzt.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Eine Änderung dieser Einstellung in der php.ini kann Ihre Nextcloud wieder lauffähig machen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ist nicht auf den erwarteten Wert „0“, sondern stattdessen auf „%s“ gesetzt", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bitte setzen Sie zum Beheben dieses Problems mbstring.func_overload in Ihrer php.ini auf 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.", + "To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, müssen Sie die libxml2 Version aktualisieren und den Webserver neustarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ist offenbar so konfiguriert, dass PHPDoc-Blöcke in der Anweisung entfernt werden. Dadurch sind mehrere Kern-Apps nicht erreichbar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dies wird wahrscheinlich durch Zwischenspeicher/Beschleuniger wie etwa Zend OPcache oder eAccelerator verursacht.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-Module wurden installiert, werden aber als noch fehlend gelistet?", + "Please ask your server administrator to restart the web server." : "Bitte kontaktieren Sie Ihren Server-Administrator und bitten Sie um den Neustart des Webservers.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 benötigt", + "Please upgrade your database version" : "Bitte aktualisieren Sie Ihre Datenbankversion", + "Your data directory is readable by other users" : "Ihr Datenverzeichnis kann von anderen Benutzern gelesen werden", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bitte ändern Sie die Berechtigungen auf 0770, so dass das Verzeichnis nicht von anderen Benutzern angezeigt werden kann.", + "Your data directory must be an absolute path" : "Ihr Datenverzeichnis muss einen eindeutigen Pfad haben", + "Check the value of \"datadirectory\" in your configuration" : "Überprüfen Sie bitte die Angabe unter „datadirectory“ in Ihrer Konfiguration", + "Your data directory is invalid" : "Ihr Datenverzeichnis ist ungültig.", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Stellen Sie sicher, dass eine Datei \".ocdata\" im Wurzelverzeichnis des data-Verzeichnisses existiert.", + "Action \"%s\" not supported or implemented." : "Aktion \"%s\" wird nicht unterstützt oder ist nicht implementiert.", + "Authentication failed, wrong token or provider ID given" : "Authentifizierung ist fehlgeschlagen. Falsches Token oder Provider-ID wurde übertragen.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Es fehlen Parameter um die Anfrage zu bearbeiten. Fehlende Parameter: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wird bereits von Cloud-Federation-Provider \"%2$s\" verwendet.", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud-Federation-Provider mit ID: \"%s\" ist nicht vorhanden.", + "Could not obtain lock type %d on \"%s\"." : "Sperrtyp %d auf „%s“ konnte nicht ermittelt werden.", + "Storage unauthorized. %s" : "Speicher nicht autorisiert. %s", + "Storage incomplete configuration. %s" : "Speicher-Konfiguration unvollständig. %s", + "Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s", + "Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar", + "Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s", + "Following databases are supported: %s" : "Die folgenden Datenbanken werden unterstützt: %s", + "Following platforms are supported: %s" : "Die folgenden Plattformen werden unterstützt: %s", + "Overview" : "Übersicht", + "Basic settings" : "Grundeinstellungen", + "Sharing" : "Teilen", + "Security" : "Sicherheit", + "Groupware" : "Groupware", + "Personal info" : "Persönliche Informationen ", + "Mobile & desktop" : "Mobil & Desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dies kann zumeist behoben werden, indem dem Web-Server Schreibzugriff auf das App-Verzeichnis eingeräumt wird. Siehe auch %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/el.js b/docker/overlays/nextcloud/html/lib/l10n/el.js new file mode 100644 index 0000000..943e498 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/el.js @@ -0,0 +1,239 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Αδυναμία εγγραφής στον κατάλογο \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Αυτό μπορεί συνήθως να διορθωθεί παρέχοντας δικαιώματα εγγραφής για το φάκελο config στο διακομιστή δικτύου", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ή εάν επιθυμείτε να διατηρήσετε το config.php σε κατάσταση ανάγνωσης μόνο, καθορίστετο από τις επιλογές του σε true του \"config_is_read_only\".", + "See %s" : "Δείτε %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Αυτό μπορεί συνήθως να διορθωθεί παρέχοντας δικαιώματα εγγραφής για το φάκελο config στον διακομιστή ιστού.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ή εάν επιθυμείτε να διατηρήσετε το config.php σε κατάσταση ανάγνωσης μόνο, καθορίστετο από τις επιλογές του σε true του \"config_is_read_only\". Δείτε %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Τα αρχεία της εφαρμογής %1$s δεν αντικαταστάθηκαν σωστά. Βεβαιωθείτε ότι πρόκειται για συμβατή έκδοση με το διακομιστή.", + "Sample configuration detected" : "Ανιχνεύθηκε δείγμα εγκατάστασης", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Έχει ανιχνευθεί ότι το δείγμα εγκατάστασης έχει αντιγραφεί. Αυτό μπορεί να σπάσει την εγκατάστασή σας και δεν υποστηρίζεται. Παρακαλούμε διαβάστε την τεκμηρίωση πριν εκτελέσετε αλλαγές στο config.php", + "Other activities" : "Άλλες δραστηριότητες", + "%1$s and %2$s" : "%1$s και %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s και %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s και %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s και %5$s", + "Education Edition" : "Εκπαιδευτική Έκδοση", + "Enterprise bundle" : "Πακέτο επιχειρήσεων", + "Groupware bundle" : "Ομάδα δέσμης", + "Hub bundle" : "Hub bundle", + "Social sharing bundle" : "Πακέτο κοινωνικού διαμοιρασμού", + "PHP %s or higher is required." : "PHP %s ή νεώτερη απαιτείται.", + "PHP with a version lower than %s is required." : "Απαιτείται PHP παλαιότερη από την έκδοση %s.", + "%sbit or higher PHP required." : "%sbit απαιτείται νεώτερη έκδοση PHP.", + "The following databases are supported: %s" : " Υποστηρίζονται οι ακόλουθες βάσεις δεδομένων: %s", + "The command line tool %s could not be found" : "Το εργαλείο γραμμής εντολών %s δεν μπορεί να βρεθεί", + "The library %s is not available." : "Το %s της βιβλιοθήκης δεν είναι διαθέσιμο.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Απαιτείται βιβλιοθήκη %1$s νεότερη από την έκδοση %2$s - διαθέσιμη έκδοση %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Απαιτείται βιβλιοθήκη %1$s παλαιότερη από την έκδοση %2$s - διαθέσιμη έκδοση %3$s.", + "The following platforms are supported: %s" : "Υποστηρίζονται οι ακόλουθες πλατφόρμες: %s", + "Server version %s or higher is required." : "Απαιτείται έκδοση διακομιστή %s ή νεότερη.", + "Server version %s or lower is required." : "Απαιτείται έκδοση διακομιστή %s ή παλαιότερη.", + "Logged in user must be an admin or sub admin" : "Ο συνδεδεμένος χρήστης πρέπει να είναι admin ή subadmin", + "Logged in user must be an admin" : "Ο συνδεδεμένος χρήστης πρέπει να είναι διαχειριστής", + "Wiping of device %s has started" : "Η εκκαθάριση συσκευής %s ξεκίνησε", + "Wiping of device »%s« has started" : "Η εκκαθάριση συσκευής »%s« ξεκίνησε", + "»%s« started remote wipe" : "»%s« ξεκίνησε η απομακρυσμένη εκκαθάριση", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Η συσκευή ή εφαρμογή »%s« ξεκίνησε απομακρυσμένη εκκαθάριση. Θα λάβετε ένα ηλ.μήνυμα μόλις ολοκληρωθεί", + "Wiping of device %s has finished" : "Η εκκαθάριση συσκευής %s ολοκληρώθηκε", + "Wiping of device »%s« has finished" : "Η εκκαθάριση συσκευής »%s« ολοκληρώθηκε", + "»%s« finished remote wipe" : "»%s« ολοκληρώθηκε η απομακρυσμένη εκκαθάριση", + "Device or application »%s« has finished the remote wipe process." : "Η συσκευή ή εφαρμογή »%s« ολοκλήρωσε την απομακρυσμένη εκκαθάριση.", + "Remote wipe started" : "Η απομακρυσμένη εκκαθάριση ξεκίνησε", + "A remote wipe was started on device %s" : "Η απομακρυσμένη εκκαθάριση ξεκίνησε στην συσκευή %s", + "Remote wipe finished" : "Η απομακρυσμένη εκκαθάριση ολοκληρώθηκε", + "The remote wipe on %s has finished" : "Η απομακρυσμένη εκκαθάριση στο %s ολοκληρώθηκε", + "Authentication" : "Πιστοποίηση", + "Unknown filetype" : "Άγνωστος τύπος αρχείου", + "Invalid image" : "Μη έγκυρη εικόνα", + "Avatar image is not square" : "Η εικόνα του άβαταρ δεν είναι τετράγωνη", + "today" : "σήμερα", + "tomorrow" : "αύριο", + "yesterday" : "χτες", + "_in %n day_::_in %n days_" : ["σε %n ημέρα","σε %n ημέρες"], + "_%n day ago_::_%n days ago_" : ["%n ημέρα πριν","%n ημέρες πριν"], + "next month" : "επόμενος μήνας", + "last month" : "τελευταίο μήνα", + "_in %n month_::_in %n months_" : ["σε %n μήνα","σε %n μήνες"], + "_%n month ago_::_%n months ago_" : ["πριν %n μήνα","πριν %n μήνες"], + "next year" : "επόμενος χρόνος", + "last year" : "τελευταίο χρόνο", + "_in %n year_::_in %n years_" : ["σε %n χρόνο","σε %n χρόνια"], + "_%n year ago_::_%n years ago_" : ["%n χρόνο πριν","%n χρόνια πριν"], + "_in %n hour_::_in %n hours_" : ["σε %n ώρα","σε %n ώρες"], + "_%n hour ago_::_%n hours ago_" : ["%nώρα πριν","%nώρες πριν"], + "_in %n minute_::_in %n minutes_" : ["σε %n λεπτό","σε %n λεπτά"], + "_%n minute ago_::_%n minutes ago_" : ["%nλεπτό πριν","%nλεπτά πριν"], + "in a few seconds" : "σε λίγα δευτερόλεπτα", + "seconds ago" : "δευτερόλεπτα πριν", + "Empty file" : "Κενό αρχείο", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Το άρθρωμα με ID: %sδεν υπάρχει. Παρακαλούμε ενεργοποιήστε το στις ρυθμίσεις των εφαρμογών σας ή επικοινωνήστε με τον διαχειριστή.", + "File name is a reserved word" : "Το όνομα αρχείου είναι λέξη που έχει δεσμευτεί", + "File name contains at least one invalid character" : "Το όνομα αρχείου περιέχει έναν τουλάχιστον μη έγκυρο χαρακτήρα", + "File name is too long" : "Το όνομα αρχείου είναι πολύ μεγάλο", + "Dot files are not allowed" : "Δεν επιτρέπονται αρχεία που ξεκινούν με τελεία", + "Empty filename is not allowed" : "Δεν επιτρέπεται άδειο όνομα αρχείου", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Η εφαρμογή \"%s\" δεν μπορεί να εγκατασταθεί διότι δεν είναι δυνατή η ανάγνωση του αρχείου appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Η εφαρμογή \"%s\" δεν μπορεί να εγκατασταθεί διότι δεν είναι συμβατή με την έκδοση του διακομιστή.", + "__language_name__" : "Ελληνικά", + "This is an automatically sent email, please do not reply." : "Αυτό είναι ένα μήνυμα ηλεκτρονικού ταχυδρομείου που στάλθηκε αυτόματα, παρακαλούμε μην απαντήσετε.", + "Help" : "Βοήθεια", + "Apps" : "Εφαρμογές", + "Settings" : "Ρυθμίσεις", + "Log out" : "Έξοδος", + "Users" : "Χρήστες", + "Unknown user" : "Άγνωστος χρήστης", + "Additional settings" : "Επιπρόσθετες ρυθμίσεις", + "%s enter the database username and name." : "%sπληκτρολογήστε όνομα χρήστη και όνομα βάσης δεδομένων.", + "%s enter the database username." : "%s εισάγετε το όνομα χρήστη της βάσης δεδομένων.", + "%s enter the database name." : "%s εισάγετε το όνομα της βάσης δεδομένων.", + "%s you may not use dots in the database name" : "%s μάλλον δεν χρησιμοποιείτε τελείες στο όνομα της βάσης δεδομένων", + "MySQL username and/or password not valid" : "Το όνομα χρήστη και/'η ο κωδικός πρόσβασης MySQL δεν είναι σωστά", + "You need to enter details of an existing account." : "Χρειάζεται να εισάγετε λεπτομέρειες από υπάρχον λογαριασμό.", + "Oracle connection could not be established" : "Αδυναμία σύνδεσης Oracle", + "Oracle username and/or password not valid" : "Μη έγκυρος χρήστης και/ή συνθηματικό της Oracle", + "PostgreSQL username and/or password not valid" : "Μη έγκυρος χρήστης και/ή συνθηματικό της PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Το Mac OS X δεν υποστηρίζεται και το %s δεν θα λειτουργήσει σωστά σε αυτή την πλατφόρμα. Χρησιμοποιείτε με δική σας ευθύνη!", + "For the best results, please consider using a GNU/Linux server instead." : "Για καλύτερα αποτελέσματα, παρακαλούμε εξετάστε την μετατροπή σε έναν διακομιστή GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Φαίνεται ότι η εγκατάσταση %s εκτελείται σε περιβάλλον 32-bit PHP και η επιλογη open_basedir έχει ρυθμιστεί στο αρχείο php.ini. Αυτό θα οδηγήσει σε προβλήματα με αρχεία πάνω από 4 GB και δεν συνίσταται.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Παρακαλούμε αφαιρέστε την ρύθμιση open_basedir μέσα στο αρχείο php.ini ή αλλάξτε σε 64-bit PHP.", + "Set an admin username." : "Εισάγετε όνομα χρήστη διαχειριστή.", + "Set an admin password." : "Εισάγετε συνθηματικό διαχειριστή.", + "Can't create or write into the data directory %s" : "Αδύνατη η δημιουργία ή συγγραφή στον κατάλογο δεδομένων %s", + "Invalid Federated Cloud ID" : "Μη έγκυρο Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Το σύστημα διαμοιρασμού %s πρέπει να υλοποιεί την διεπαφή OCP\\Share_Backend", + "Sharing backend %s not found" : "Το σύστημα διαμοιρασμού %s δεν βρέθηκε", + "Sharing backend for %s not found" : "Το σύστημα διαμοιρασμού για το %s δεν βρέθηκε", + "%1$s shared »%2$s« with you and wants to add:" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς και θέλει να προσθέσει:", + "%1$s shared »%2$s« with you and wants to add" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς και θέλει να προσθέσει", + "»%s« added a note to a file shared with you" : "Ο »%s« πρόσθεσε μια σημείωση στο κοινόχρηστο αρχείο", + "Open »%s«" : "Άνοιγμα »%s«", + "%1$s via %2$s" : "%1$s μέσω %2$s", + "You are not allowed to share %s" : "Δεν σας επιτρέπεται ο επαναδιαμοιρασμός %s", + "Can’t increase permissions of %s" : "Αδυναμία αύξησης των δικαιωμάτων του %s", + "Files can’t be shared with delete permissions" : "Δεν μπορεί να γίνει διαμοιρασμός αρχείων με δικαιώματα διαγραφής", + "Files can’t be shared with create permissions" : "Δεν μπορεί να γίνει διαμοιρασμός αρχείων με δικαιώματα δημιουργίας", + "Expiration date is in the past" : "Η ημερομηνία λήξης είναι στο παρελθόν", + "Can’t set expiration date more than %s days in the future" : "Δεν είναι δυνατό να τεθεί η ημερομηνία λήξης σε περισσότερες από %s ημέρες στο μέλλον", + "%1$s shared »%2$s« with you" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς.", + "%1$s shared »%2$s« with you." : "%1$s διαμοιράστηκε »%2$s« με εσάς.", + "Click the button below to open it." : "Κάντε κλικ στο παρακάτω κουμπί για να το ανοίξετε.", + "The requested share does not exist anymore" : "Το διαμοιρασμένο που ζητήθηκε δεν υπάρχει πλέον", + "Could not find category \"%s\"" : "Αδυναμία εύρεσης κατηγορίας \"%s\"", + "Sunday" : "Κυριακή", + "Monday" : "Δευτέρα", + "Tuesday" : "Τρίτη", + "Wednesday" : "Τετάρτη", + "Thursday" : "Πέμπτη", + "Friday" : "Παρασκευή", + "Saturday" : "Σάββατο", + "Sun." : "Κυρ.", + "Mon." : "Δευ.", + "Tue." : "Τρί.", + "Wed." : "Τετ.", + "Thu." : "Πέμ.", + "Fri." : "Παρ.", + "Sat." : "Σαβ.", + "Su" : "Κυ", + "Mo" : "Δε", + "Tu" : "Τρ", + "We" : "Τε", + "Th" : "Πε", + "Fr" : "Πα", + "Sa" : "Σα", + "January" : "Ιανουάριος", + "February" : "Φεβρουάριος", + "March" : "Μάρτιος", + "April" : "Απρίλιος", + "May" : "Μάϊος", + "June" : "Ιούνιος", + "July" : "Ιούλιος", + "August" : "Αύγουστος", + "September" : "Σεπτέμβριος", + "October" : "Οκτώβριος", + "November" : "Νοέμβριος", + "December" : "Δεκέμβριος", + "Jan." : "Ιαν.", + "Feb." : "Φεβ.", + "Mar." : "Μαρ.", + "Apr." : "Απρ.", + "May." : "Μαι.", + "Jun." : "Ιουν.", + "Jul." : "Ιουλ.", + "Aug." : "Αυγ.", + "Sep." : "Σεπ.", + "Oct." : "Οκτ.", + "Nov." : "Νοε.", + "Dec." : "Δεκ.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Μόνο οι ακόλουθοι χαρακτήρες επιτρέπονται στο όνομα χρήστη; \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Πρέπει να δοθεί έγκυρο όνομα χρήστη", + "Username contains whitespace at the beginning or at the end" : "Το όνομα χρήστη περιέχει κενό διάστημα στην αρχή ή στο τέλος", + "Username must not consist of dots only" : "Το όνομα χρήστη δεν πρέπει να περιέχει μόνο τελείες", + "Username is invalid because files already exist for this user" : "Το όνομα χρήστη δεν είναι έγκυρο, επειδή υπάρχουν ήδη αρχεία για αυτόν τον χρήστη", + "A valid password must be provided" : "Πρέπει να δοθεί έγκυρο συνθηματικό", + "The username is already being used" : "Το όνομα χρήστη είναι κατειλημμένο", + "Could not create user" : "Αδυναμία δημιουργίας χρήστη", + "User disabled" : "Ο χρήστης απενεργοποιήθηκε", + "Login canceled by app" : "Η είσοδος ακυρώθηκε από την εφαρμογή", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Η εφαρμογή \"%1$s\" δεν μπορεί να εγκατασταθεί επειδή δεν πληρούνται τα προαπαιτούμενα: %2$s", + "a safe home for all your data" : "ένα ασφαλές μέρος για όλα τα δεδομένα σας", + "File is currently busy, please try again later" : "Το αρχείο χρησιμοποιείται αυτή τη στιγμή, παρακαλούμε προσπαθήστε αργότερα", + "Can't read file" : "Αδυναμία ανάγνωσης αρχείου", + "Application is not enabled" : "Δεν ενεργοποιήθηκε η εφαρμογή", + "Authentication error" : "Σφάλμα πιστοποίησης", + "Token expired. Please reload page." : "Το αναγνωριστικό έληξε. Παρακαλούμε φορτώστε ξανά την σελίδα.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Δεν βρέθηκαν εγκατεστημένοι οδηγοί βάσεων δεδομένων (sqlite, mysql, or postgresql).", + "Cannot write into \"config\" directory" : "Αδυναμία εγγραφής στον κατάλογο \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας στον διακομιστή γραπτή πρόσβαση στον κατάλογο εκχώρησης. Βλέπε%s", + "Cannot write into \"apps\" directory" : "Αδυναμία εγγραφής στον κατάλογο \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας δικαιώματα εγγραφής για τον κατάλογο εφαρμογών στον διακομιστή ιστού ή απενεργοποιώντας το κέντρο εφαρμογών στο αρχείο config.", + "Cannot create \"data\" directory" : "Αδυναμία δημιουργίας του καταλόγου \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας στον διακομιστή ιστού δικαιώματα εγγραφής στον βασικό κατάλογο. Δείτε το%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Τα δικαιώματα πρόσβασης μπορούν συνήθως να διορθωθούν δίνοντας δικαιώματα εγγραφής στον βασικό κατάλογο στον διακομιστή ιστού. Δείτε το%s.", + "Setting locale to %s failed" : "Ρύθμιση τοπικών ρυθμίσεων σε %s απέτυχε", + "Please install one of these locales on your system and restart your webserver." : "Παρακαλούμε να εγκαταστήσετε μία από αυτές τις τοπικές ρυθμίσεις στο σύστημά σας και να επανεκκινήσετε τον διακομιστή δικτύου σας.", + "PHP module %s not installed." : "Η μονάδα %s PHP δεν είναι εγκατεστημένη. ", + "Please ask your server administrator to install the module." : "Παρακαλούμε ζητήστε από το διαχειριστή του διακομιστή σας να εγκαταστήσει τη μονάδα.", + "PHP setting \"%s\" is not set to \"%s\"." : "Η ρύθμιση \"%s\"της PHP δεν είναι ορισμένη σε \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Προσαρμόζοντας αυτήν τη ρύθμιση στο php.ini το Nextcloud θα εκτελεστεί ξανά", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "Το mbstring.func_overload έχει ορισθεί σε \"%s\" αντί για την αναμενόμενη τιμή \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Για να διορθώσετε αυτό το πρόβλημα ορίστε το mbstring.func_overload σε 0 στο αρχείο php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Απαιτείται τουλάχιστον το libxml2 2.7.0. Αυτή τη στιγμή είναι εγκατεστημένο το %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Για να διορθώσετε το σφάλμα ενημερώστε την έκδοση του libxml2 και επανεκκινήστε τον διακομιστή.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Η PHP φαίνεται να είναι ρυθμισμένη ώστε να αφαιρεί inline doc blocks. Αυτό θα καταστήσει πολλές βασικές εφαρμογές μη διαθέσιμες.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Αυτό πιθανόν προκλήθηκε από προσωρινή μνήμη (cache)/επιταχυντή όπως τη Zend OPcache ή τον eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Κάποια αρθρώματα της PHP έχουν εγκατασταθεί, αλλά είναι ακόμα καταγεγραμμένες ως εκλιπόντα;", + "Please ask your server administrator to restart the web server." : "Παρακαλούμε ζητήστε από το διαχειριστή του διακομιστή σας να επανεκκινήσει το διακομιστή δικτύου σας.", + "PostgreSQL >= 9 required" : "Απαιτείται PostgreSQL >= 9", + "Please upgrade your database version" : "Παρακαλούμε αναβαθμίστε την έκδοση της βάσης δεδομένων σας", + "Your data directory is readable by other users" : "Ο κατάλογος δεδομένων σας είναι διαθέσιμος προς ανάγνωση από άλλους χρήστες", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Παρακαλούμε αλλάξτε τις ρυθμίσεις σε 0770 έτσι ώστε ο κατάλογος να μην μπορεί να προβάλλεται από άλλους χρήστες.", + "Your data directory must be an absolute path" : "Ο κατάλογος δεδομένων σας πρέπει να είναι απόλυτη διαδρομή", + "Check the value of \"datadirectory\" in your configuration" : "Ελέγξτε την τιμή του \"Φάκελος Δεδομένων\" στις ρυθμίσεις σας", + "Your data directory is invalid" : "Ο κατάλογος δεδομένων σας δεν είναι έγκυρος", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Εξασφαλίστε ότι υπάρχει ένα αρχείο με όνομα \".ocdata\" στον βασικό κατάλογο του καταλόγου δεδομένων.", + "Action \"%s\" not supported or implemented." : "Η ενέργεια \"%s\" δεν υποστηρίζεται ή δεν μπορεί να υλοποιηθεί.", + "Authentication failed, wrong token or provider ID given" : "Ο έλεγχος ταυτότητας απέτυχε, δόθηκε λανθασμένο αναγνωριστικό ή αναγνωριστικό παρόχου", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Απουσιάζουν παράμετροι για την ολοκλήρωση του αιτήματος. Ελλιπής παράμετροι: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Το αναγνωριστικό \"%1$s\" χρησιμοποιείται ήδη από τον ομόσπονδο πάροχο \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Αναγνωριστικό Ομόσπονδου Παρόχου Σύννεφου: \"%s\" δεν υπάρχει.", + "Could not obtain lock type %d on \"%s\"." : "Αδυναμία ανάκτησης τύπου κλειδιού %d στο \"%s\".", + "Storage unauthorized. %s" : "Αποθηκευτικός χώρος χωρίς εξουσιοδότηση. %s", + "Storage incomplete configuration. %s" : "Ελλιπής διαμόρφωση αποθηκευτικού χώρου. %s", + "Storage connection error. %s" : "Σφάλμα σύνδεσης με αποθηκευτικό χώρο. %s", + "Storage is temporarily not available" : "Μη διαθέσιμος χώρος αποθήκευσης προσωρινά", + "Storage connection timeout. %s" : "Λήξη χρονικού ορίου σύνδεσης με αποθηκευτικό χώρο.%s", + "Following databases are supported: %s" : " Υποστηρίζονται οι ακόλουθες βάσεις δεδομένων: %s", + "Following platforms are supported: %s" : "Οι ακόλουθες πλατφόρμες υποστηρίζονται: %s", + "Overview" : "Επισκόπηση", + "Basic settings" : "Βασικές ρυθμίσεις", + "Sharing" : "Διαμοιρασμός", + "Security" : "Ασφάλεια", + "Groupware" : "Ομαδικό", + "Personal info" : "Προσωπικές πληροφορίες", + "Mobile & desktop" : "Κινητό & σταθερό", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Αυτό συνήθως μπορεί να διορθωθεί δίνοντας δικαιώματα εγγραφής στον κατάλογο apps στον διακομιστή ιστού ή απενεργοποιώντας το appstore στο αρχείο διαμόρφωσης. Δείτε το %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/el.json b/docker/overlays/nextcloud/html/lib/l10n/el.json new file mode 100644 index 0000000..7d5867e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/el.json @@ -0,0 +1,237 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Αδυναμία εγγραφής στον κατάλογο \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Αυτό μπορεί συνήθως να διορθωθεί παρέχοντας δικαιώματα εγγραφής για το φάκελο config στο διακομιστή δικτύου", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ή εάν επιθυμείτε να διατηρήσετε το config.php σε κατάσταση ανάγνωσης μόνο, καθορίστετο από τις επιλογές του σε true του \"config_is_read_only\".", + "See %s" : "Δείτε %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Αυτό μπορεί συνήθως να διορθωθεί παρέχοντας δικαιώματα εγγραφής για το φάκελο config στον διακομιστή ιστού.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ή εάν επιθυμείτε να διατηρήσετε το config.php σε κατάσταση ανάγνωσης μόνο, καθορίστετο από τις επιλογές του σε true του \"config_is_read_only\". Δείτε %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Τα αρχεία της εφαρμογής %1$s δεν αντικαταστάθηκαν σωστά. Βεβαιωθείτε ότι πρόκειται για συμβατή έκδοση με το διακομιστή.", + "Sample configuration detected" : "Ανιχνεύθηκε δείγμα εγκατάστασης", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Έχει ανιχνευθεί ότι το δείγμα εγκατάστασης έχει αντιγραφεί. Αυτό μπορεί να σπάσει την εγκατάστασή σας και δεν υποστηρίζεται. Παρακαλούμε διαβάστε την τεκμηρίωση πριν εκτελέσετε αλλαγές στο config.php", + "Other activities" : "Άλλες δραστηριότητες", + "%1$s and %2$s" : "%1$s και %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s και %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s και %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s και %5$s", + "Education Edition" : "Εκπαιδευτική Έκδοση", + "Enterprise bundle" : "Πακέτο επιχειρήσεων", + "Groupware bundle" : "Ομάδα δέσμης", + "Hub bundle" : "Hub bundle", + "Social sharing bundle" : "Πακέτο κοινωνικού διαμοιρασμού", + "PHP %s or higher is required." : "PHP %s ή νεώτερη απαιτείται.", + "PHP with a version lower than %s is required." : "Απαιτείται PHP παλαιότερη από την έκδοση %s.", + "%sbit or higher PHP required." : "%sbit απαιτείται νεώτερη έκδοση PHP.", + "The following databases are supported: %s" : " Υποστηρίζονται οι ακόλουθες βάσεις δεδομένων: %s", + "The command line tool %s could not be found" : "Το εργαλείο γραμμής εντολών %s δεν μπορεί να βρεθεί", + "The library %s is not available." : "Το %s της βιβλιοθήκης δεν είναι διαθέσιμο.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Απαιτείται βιβλιοθήκη %1$s νεότερη από την έκδοση %2$s - διαθέσιμη έκδοση %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Απαιτείται βιβλιοθήκη %1$s παλαιότερη από την έκδοση %2$s - διαθέσιμη έκδοση %3$s.", + "The following platforms are supported: %s" : "Υποστηρίζονται οι ακόλουθες πλατφόρμες: %s", + "Server version %s or higher is required." : "Απαιτείται έκδοση διακομιστή %s ή νεότερη.", + "Server version %s or lower is required." : "Απαιτείται έκδοση διακομιστή %s ή παλαιότερη.", + "Logged in user must be an admin or sub admin" : "Ο συνδεδεμένος χρήστης πρέπει να είναι admin ή subadmin", + "Logged in user must be an admin" : "Ο συνδεδεμένος χρήστης πρέπει να είναι διαχειριστής", + "Wiping of device %s has started" : "Η εκκαθάριση συσκευής %s ξεκίνησε", + "Wiping of device »%s« has started" : "Η εκκαθάριση συσκευής »%s« ξεκίνησε", + "»%s« started remote wipe" : "»%s« ξεκίνησε η απομακρυσμένη εκκαθάριση", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Η συσκευή ή εφαρμογή »%s« ξεκίνησε απομακρυσμένη εκκαθάριση. Θα λάβετε ένα ηλ.μήνυμα μόλις ολοκληρωθεί", + "Wiping of device %s has finished" : "Η εκκαθάριση συσκευής %s ολοκληρώθηκε", + "Wiping of device »%s« has finished" : "Η εκκαθάριση συσκευής »%s« ολοκληρώθηκε", + "»%s« finished remote wipe" : "»%s« ολοκληρώθηκε η απομακρυσμένη εκκαθάριση", + "Device or application »%s« has finished the remote wipe process." : "Η συσκευή ή εφαρμογή »%s« ολοκλήρωσε την απομακρυσμένη εκκαθάριση.", + "Remote wipe started" : "Η απομακρυσμένη εκκαθάριση ξεκίνησε", + "A remote wipe was started on device %s" : "Η απομακρυσμένη εκκαθάριση ξεκίνησε στην συσκευή %s", + "Remote wipe finished" : "Η απομακρυσμένη εκκαθάριση ολοκληρώθηκε", + "The remote wipe on %s has finished" : "Η απομακρυσμένη εκκαθάριση στο %s ολοκληρώθηκε", + "Authentication" : "Πιστοποίηση", + "Unknown filetype" : "Άγνωστος τύπος αρχείου", + "Invalid image" : "Μη έγκυρη εικόνα", + "Avatar image is not square" : "Η εικόνα του άβαταρ δεν είναι τετράγωνη", + "today" : "σήμερα", + "tomorrow" : "αύριο", + "yesterday" : "χτες", + "_in %n day_::_in %n days_" : ["σε %n ημέρα","σε %n ημέρες"], + "_%n day ago_::_%n days ago_" : ["%n ημέρα πριν","%n ημέρες πριν"], + "next month" : "επόμενος μήνας", + "last month" : "τελευταίο μήνα", + "_in %n month_::_in %n months_" : ["σε %n μήνα","σε %n μήνες"], + "_%n month ago_::_%n months ago_" : ["πριν %n μήνα","πριν %n μήνες"], + "next year" : "επόμενος χρόνος", + "last year" : "τελευταίο χρόνο", + "_in %n year_::_in %n years_" : ["σε %n χρόνο","σε %n χρόνια"], + "_%n year ago_::_%n years ago_" : ["%n χρόνο πριν","%n χρόνια πριν"], + "_in %n hour_::_in %n hours_" : ["σε %n ώρα","σε %n ώρες"], + "_%n hour ago_::_%n hours ago_" : ["%nώρα πριν","%nώρες πριν"], + "_in %n minute_::_in %n minutes_" : ["σε %n λεπτό","σε %n λεπτά"], + "_%n minute ago_::_%n minutes ago_" : ["%nλεπτό πριν","%nλεπτά πριν"], + "in a few seconds" : "σε λίγα δευτερόλεπτα", + "seconds ago" : "δευτερόλεπτα πριν", + "Empty file" : "Κενό αρχείο", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Το άρθρωμα με ID: %sδεν υπάρχει. Παρακαλούμε ενεργοποιήστε το στις ρυθμίσεις των εφαρμογών σας ή επικοινωνήστε με τον διαχειριστή.", + "File name is a reserved word" : "Το όνομα αρχείου είναι λέξη που έχει δεσμευτεί", + "File name contains at least one invalid character" : "Το όνομα αρχείου περιέχει έναν τουλάχιστον μη έγκυρο χαρακτήρα", + "File name is too long" : "Το όνομα αρχείου είναι πολύ μεγάλο", + "Dot files are not allowed" : "Δεν επιτρέπονται αρχεία που ξεκινούν με τελεία", + "Empty filename is not allowed" : "Δεν επιτρέπεται άδειο όνομα αρχείου", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Η εφαρμογή \"%s\" δεν μπορεί να εγκατασταθεί διότι δεν είναι δυνατή η ανάγνωση του αρχείου appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Η εφαρμογή \"%s\" δεν μπορεί να εγκατασταθεί διότι δεν είναι συμβατή με την έκδοση του διακομιστή.", + "__language_name__" : "Ελληνικά", + "This is an automatically sent email, please do not reply." : "Αυτό είναι ένα μήνυμα ηλεκτρονικού ταχυδρομείου που στάλθηκε αυτόματα, παρακαλούμε μην απαντήσετε.", + "Help" : "Βοήθεια", + "Apps" : "Εφαρμογές", + "Settings" : "Ρυθμίσεις", + "Log out" : "Έξοδος", + "Users" : "Χρήστες", + "Unknown user" : "Άγνωστος χρήστης", + "Additional settings" : "Επιπρόσθετες ρυθμίσεις", + "%s enter the database username and name." : "%sπληκτρολογήστε όνομα χρήστη και όνομα βάσης δεδομένων.", + "%s enter the database username." : "%s εισάγετε το όνομα χρήστη της βάσης δεδομένων.", + "%s enter the database name." : "%s εισάγετε το όνομα της βάσης δεδομένων.", + "%s you may not use dots in the database name" : "%s μάλλον δεν χρησιμοποιείτε τελείες στο όνομα της βάσης δεδομένων", + "MySQL username and/or password not valid" : "Το όνομα χρήστη και/'η ο κωδικός πρόσβασης MySQL δεν είναι σωστά", + "You need to enter details of an existing account." : "Χρειάζεται να εισάγετε λεπτομέρειες από υπάρχον λογαριασμό.", + "Oracle connection could not be established" : "Αδυναμία σύνδεσης Oracle", + "Oracle username and/or password not valid" : "Μη έγκυρος χρήστης και/ή συνθηματικό της Oracle", + "PostgreSQL username and/or password not valid" : "Μη έγκυρος χρήστης και/ή συνθηματικό της PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Το Mac OS X δεν υποστηρίζεται και το %s δεν θα λειτουργήσει σωστά σε αυτή την πλατφόρμα. Χρησιμοποιείτε με δική σας ευθύνη!", + "For the best results, please consider using a GNU/Linux server instead." : "Για καλύτερα αποτελέσματα, παρακαλούμε εξετάστε την μετατροπή σε έναν διακομιστή GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Φαίνεται ότι η εγκατάσταση %s εκτελείται σε περιβάλλον 32-bit PHP και η επιλογη open_basedir έχει ρυθμιστεί στο αρχείο php.ini. Αυτό θα οδηγήσει σε προβλήματα με αρχεία πάνω από 4 GB και δεν συνίσταται.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Παρακαλούμε αφαιρέστε την ρύθμιση open_basedir μέσα στο αρχείο php.ini ή αλλάξτε σε 64-bit PHP.", + "Set an admin username." : "Εισάγετε όνομα χρήστη διαχειριστή.", + "Set an admin password." : "Εισάγετε συνθηματικό διαχειριστή.", + "Can't create or write into the data directory %s" : "Αδύνατη η δημιουργία ή συγγραφή στον κατάλογο δεδομένων %s", + "Invalid Federated Cloud ID" : "Μη έγκυρο Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Το σύστημα διαμοιρασμού %s πρέπει να υλοποιεί την διεπαφή OCP\\Share_Backend", + "Sharing backend %s not found" : "Το σύστημα διαμοιρασμού %s δεν βρέθηκε", + "Sharing backend for %s not found" : "Το σύστημα διαμοιρασμού για το %s δεν βρέθηκε", + "%1$s shared »%2$s« with you and wants to add:" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς και θέλει να προσθέσει:", + "%1$s shared »%2$s« with you and wants to add" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς και θέλει να προσθέσει", + "»%s« added a note to a file shared with you" : "Ο »%s« πρόσθεσε μια σημείωση στο κοινόχρηστο αρχείο", + "Open »%s«" : "Άνοιγμα »%s«", + "%1$s via %2$s" : "%1$s μέσω %2$s", + "You are not allowed to share %s" : "Δεν σας επιτρέπεται ο επαναδιαμοιρασμός %s", + "Can’t increase permissions of %s" : "Αδυναμία αύξησης των δικαιωμάτων του %s", + "Files can’t be shared with delete permissions" : "Δεν μπορεί να γίνει διαμοιρασμός αρχείων με δικαιώματα διαγραφής", + "Files can’t be shared with create permissions" : "Δεν μπορεί να γίνει διαμοιρασμός αρχείων με δικαιώματα δημιουργίας", + "Expiration date is in the past" : "Η ημερομηνία λήξης είναι στο παρελθόν", + "Can’t set expiration date more than %s days in the future" : "Δεν είναι δυνατό να τεθεί η ημερομηνία λήξης σε περισσότερες από %s ημέρες στο μέλλον", + "%1$s shared »%2$s« with you" : "Ο %1$s διαμοιράστηκε το »%2$s« με εσάς.", + "%1$s shared »%2$s« with you." : "%1$s διαμοιράστηκε »%2$s« με εσάς.", + "Click the button below to open it." : "Κάντε κλικ στο παρακάτω κουμπί για να το ανοίξετε.", + "The requested share does not exist anymore" : "Το διαμοιρασμένο που ζητήθηκε δεν υπάρχει πλέον", + "Could not find category \"%s\"" : "Αδυναμία εύρεσης κατηγορίας \"%s\"", + "Sunday" : "Κυριακή", + "Monday" : "Δευτέρα", + "Tuesday" : "Τρίτη", + "Wednesday" : "Τετάρτη", + "Thursday" : "Πέμπτη", + "Friday" : "Παρασκευή", + "Saturday" : "Σάββατο", + "Sun." : "Κυρ.", + "Mon." : "Δευ.", + "Tue." : "Τρί.", + "Wed." : "Τετ.", + "Thu." : "Πέμ.", + "Fri." : "Παρ.", + "Sat." : "Σαβ.", + "Su" : "Κυ", + "Mo" : "Δε", + "Tu" : "Τρ", + "We" : "Τε", + "Th" : "Πε", + "Fr" : "Πα", + "Sa" : "Σα", + "January" : "Ιανουάριος", + "February" : "Φεβρουάριος", + "March" : "Μάρτιος", + "April" : "Απρίλιος", + "May" : "Μάϊος", + "June" : "Ιούνιος", + "July" : "Ιούλιος", + "August" : "Αύγουστος", + "September" : "Σεπτέμβριος", + "October" : "Οκτώβριος", + "November" : "Νοέμβριος", + "December" : "Δεκέμβριος", + "Jan." : "Ιαν.", + "Feb." : "Φεβ.", + "Mar." : "Μαρ.", + "Apr." : "Απρ.", + "May." : "Μαι.", + "Jun." : "Ιουν.", + "Jul." : "Ιουλ.", + "Aug." : "Αυγ.", + "Sep." : "Σεπ.", + "Oct." : "Οκτ.", + "Nov." : "Νοε.", + "Dec." : "Δεκ.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Μόνο οι ακόλουθοι χαρακτήρες επιτρέπονται στο όνομα χρήστη; \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Πρέπει να δοθεί έγκυρο όνομα χρήστη", + "Username contains whitespace at the beginning or at the end" : "Το όνομα χρήστη περιέχει κενό διάστημα στην αρχή ή στο τέλος", + "Username must not consist of dots only" : "Το όνομα χρήστη δεν πρέπει να περιέχει μόνο τελείες", + "Username is invalid because files already exist for this user" : "Το όνομα χρήστη δεν είναι έγκυρο, επειδή υπάρχουν ήδη αρχεία για αυτόν τον χρήστη", + "A valid password must be provided" : "Πρέπει να δοθεί έγκυρο συνθηματικό", + "The username is already being used" : "Το όνομα χρήστη είναι κατειλημμένο", + "Could not create user" : "Αδυναμία δημιουργίας χρήστη", + "User disabled" : "Ο χρήστης απενεργοποιήθηκε", + "Login canceled by app" : "Η είσοδος ακυρώθηκε από την εφαρμογή", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Η εφαρμογή \"%1$s\" δεν μπορεί να εγκατασταθεί επειδή δεν πληρούνται τα προαπαιτούμενα: %2$s", + "a safe home for all your data" : "ένα ασφαλές μέρος για όλα τα δεδομένα σας", + "File is currently busy, please try again later" : "Το αρχείο χρησιμοποιείται αυτή τη στιγμή, παρακαλούμε προσπαθήστε αργότερα", + "Can't read file" : "Αδυναμία ανάγνωσης αρχείου", + "Application is not enabled" : "Δεν ενεργοποιήθηκε η εφαρμογή", + "Authentication error" : "Σφάλμα πιστοποίησης", + "Token expired. Please reload page." : "Το αναγνωριστικό έληξε. Παρακαλούμε φορτώστε ξανά την σελίδα.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Δεν βρέθηκαν εγκατεστημένοι οδηγοί βάσεων δεδομένων (sqlite, mysql, or postgresql).", + "Cannot write into \"config\" directory" : "Αδυναμία εγγραφής στον κατάλογο \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας στον διακομιστή γραπτή πρόσβαση στον κατάλογο εκχώρησης. Βλέπε%s", + "Cannot write into \"apps\" directory" : "Αδυναμία εγγραφής στον κατάλογο \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας δικαιώματα εγγραφής για τον κατάλογο εφαρμογών στον διακομιστή ιστού ή απενεργοποιώντας το κέντρο εφαρμογών στο αρχείο config.", + "Cannot create \"data\" directory" : "Αδυναμία δημιουργίας του καταλόγου \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Αυτό μπορεί συνήθως να διορθωθεί δίνοντας στον διακομιστή ιστού δικαιώματα εγγραφής στον βασικό κατάλογο. Δείτε το%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Τα δικαιώματα πρόσβασης μπορούν συνήθως να διορθωθούν δίνοντας δικαιώματα εγγραφής στον βασικό κατάλογο στον διακομιστή ιστού. Δείτε το%s.", + "Setting locale to %s failed" : "Ρύθμιση τοπικών ρυθμίσεων σε %s απέτυχε", + "Please install one of these locales on your system and restart your webserver." : "Παρακαλούμε να εγκαταστήσετε μία από αυτές τις τοπικές ρυθμίσεις στο σύστημά σας και να επανεκκινήσετε τον διακομιστή δικτύου σας.", + "PHP module %s not installed." : "Η μονάδα %s PHP δεν είναι εγκατεστημένη. ", + "Please ask your server administrator to install the module." : "Παρακαλούμε ζητήστε από το διαχειριστή του διακομιστή σας να εγκαταστήσει τη μονάδα.", + "PHP setting \"%s\" is not set to \"%s\"." : "Η ρύθμιση \"%s\"της PHP δεν είναι ορισμένη σε \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Προσαρμόζοντας αυτήν τη ρύθμιση στο php.ini το Nextcloud θα εκτελεστεί ξανά", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "Το mbstring.func_overload έχει ορισθεί σε \"%s\" αντί για την αναμενόμενη τιμή \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Για να διορθώσετε αυτό το πρόβλημα ορίστε το mbstring.func_overload σε 0 στο αρχείο php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Απαιτείται τουλάχιστον το libxml2 2.7.0. Αυτή τη στιγμή είναι εγκατεστημένο το %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Για να διορθώσετε το σφάλμα ενημερώστε την έκδοση του libxml2 και επανεκκινήστε τον διακομιστή.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Η PHP φαίνεται να είναι ρυθμισμένη ώστε να αφαιρεί inline doc blocks. Αυτό θα καταστήσει πολλές βασικές εφαρμογές μη διαθέσιμες.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Αυτό πιθανόν προκλήθηκε από προσωρινή μνήμη (cache)/επιταχυντή όπως τη Zend OPcache ή τον eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Κάποια αρθρώματα της PHP έχουν εγκατασταθεί, αλλά είναι ακόμα καταγεγραμμένες ως εκλιπόντα;", + "Please ask your server administrator to restart the web server." : "Παρακαλούμε ζητήστε από το διαχειριστή του διακομιστή σας να επανεκκινήσει το διακομιστή δικτύου σας.", + "PostgreSQL >= 9 required" : "Απαιτείται PostgreSQL >= 9", + "Please upgrade your database version" : "Παρακαλούμε αναβαθμίστε την έκδοση της βάσης δεδομένων σας", + "Your data directory is readable by other users" : "Ο κατάλογος δεδομένων σας είναι διαθέσιμος προς ανάγνωση από άλλους χρήστες", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Παρακαλούμε αλλάξτε τις ρυθμίσεις σε 0770 έτσι ώστε ο κατάλογος να μην μπορεί να προβάλλεται από άλλους χρήστες.", + "Your data directory must be an absolute path" : "Ο κατάλογος δεδομένων σας πρέπει να είναι απόλυτη διαδρομή", + "Check the value of \"datadirectory\" in your configuration" : "Ελέγξτε την τιμή του \"Φάκελος Δεδομένων\" στις ρυθμίσεις σας", + "Your data directory is invalid" : "Ο κατάλογος δεδομένων σας δεν είναι έγκυρος", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Εξασφαλίστε ότι υπάρχει ένα αρχείο με όνομα \".ocdata\" στον βασικό κατάλογο του καταλόγου δεδομένων.", + "Action \"%s\" not supported or implemented." : "Η ενέργεια \"%s\" δεν υποστηρίζεται ή δεν μπορεί να υλοποιηθεί.", + "Authentication failed, wrong token or provider ID given" : "Ο έλεγχος ταυτότητας απέτυχε, δόθηκε λανθασμένο αναγνωριστικό ή αναγνωριστικό παρόχου", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Απουσιάζουν παράμετροι για την ολοκλήρωση του αιτήματος. Ελλιπής παράμετροι: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Το αναγνωριστικό \"%1$s\" χρησιμοποιείται ήδη από τον ομόσπονδο πάροχο \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Αναγνωριστικό Ομόσπονδου Παρόχου Σύννεφου: \"%s\" δεν υπάρχει.", + "Could not obtain lock type %d on \"%s\"." : "Αδυναμία ανάκτησης τύπου κλειδιού %d στο \"%s\".", + "Storage unauthorized. %s" : "Αποθηκευτικός χώρος χωρίς εξουσιοδότηση. %s", + "Storage incomplete configuration. %s" : "Ελλιπής διαμόρφωση αποθηκευτικού χώρου. %s", + "Storage connection error. %s" : "Σφάλμα σύνδεσης με αποθηκευτικό χώρο. %s", + "Storage is temporarily not available" : "Μη διαθέσιμος χώρος αποθήκευσης προσωρινά", + "Storage connection timeout. %s" : "Λήξη χρονικού ορίου σύνδεσης με αποθηκευτικό χώρο.%s", + "Following databases are supported: %s" : " Υποστηρίζονται οι ακόλουθες βάσεις δεδομένων: %s", + "Following platforms are supported: %s" : "Οι ακόλουθες πλατφόρμες υποστηρίζονται: %s", + "Overview" : "Επισκόπηση", + "Basic settings" : "Βασικές ρυθμίσεις", + "Sharing" : "Διαμοιρασμός", + "Security" : "Ασφάλεια", + "Groupware" : "Ομαδικό", + "Personal info" : "Προσωπικές πληροφορίες", + "Mobile & desktop" : "Κινητό & σταθερό", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Αυτό συνήθως μπορεί να διορθωθεί δίνοντας δικαιώματα εγγραφής στον κατάλογο apps στον διακομιστή ιστού ή απενεργοποιώντας το appstore στο αρχείο διαμόρφωσης. Δείτε το %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/en_GB.js b/docker/overlays/nextcloud/html/lib/l10n/en_GB.js new file mode 100644 index 0000000..c60b552 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/en_GB.js @@ -0,0 +1,200 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Cannot write into \"config\" directory!", + "This can usually be fixed by giving the webserver write access to the config directory" : "This can usually be fixed by giving the webserver write access to the config directory", + "See %s" : "See %s", + "Sample configuration detected" : "Sample configuration detected", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php", + "%1$s and %2$s" : "%1$s and %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s and %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s and %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s and %5$s", + "Education Edition" : "Education Edition", + "Enterprise bundle" : "Enterprise bundle", + "Groupware bundle" : "Groupware bundle", + "Social sharing bundle" : "Social sharing bundle", + "PHP %s or higher is required." : "PHP %s or higher is required.", + "PHP with a version lower than %s is required." : "PHP with a version lower than %s is required.", + "%sbit or higher PHP required." : "%sbit or higher PHP required.", + "The command line tool %s could not be found" : "The command line tool %s could not be found", + "The library %s is not available." : "The library %s is not available.", + "Server version %s or higher is required." : "Server version %s or higher is required.", + "Server version %s or lower is required." : "Server version %s or lower is required.", + "Logged in user must be an admin" : "Logged in user must be an admin", + "Authentication" : "Authentication", + "Unknown filetype" : "Unknown filetype", + "Invalid image" : "Invalid image", + "Avatar image is not square" : "Avatar image is not square", + "today" : "today", + "tomorrow" : "tomorrow", + "yesterday" : "yesterday", + "_in %n day_::_in %n days_" : ["in %n day","in %n days"], + "_%n day ago_::_%n days ago_" : ["%n day ago","%n days ago"], + "next month" : "next month", + "last month" : "last month", + "_in %n month_::_in %n months_" : ["in %n month","in %n months"], + "_%n month ago_::_%n months ago_" : ["%n month ago","%n months ago"], + "next year" : "next year", + "last year" : "last year", + "_in %n year_::_in %n years_" : ["in %n year","in %n years"], + "_%n year ago_::_%n years ago_" : ["%n year ago","%n years ago"], + "_in %n hour_::_in %n hours_" : ["in %n hour","in %n hours"], + "_%n hour ago_::_%n hours ago_" : ["%n hour ago","%n hours ago"], + "_in %n minute_::_in %n minutes_" : ["in %n minute","in %n minutes"], + "_%n minute ago_::_%n minutes ago_" : ["%n minute ago","%n minutes ago"], + "in a few seconds" : "in a few seconds", + "seconds ago" : "seconds ago", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.", + "File name is a reserved word" : "File name is a reserved word", + "File name contains at least one invalid character" : "File name contains at least one invalid character", + "File name is too long" : "File name is too long", + "Dot files are not allowed" : "Dot files are not allowed", + "Empty filename is not allowed" : "Empty filename is not allowed", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" cannot be installed because appinfo file cannot be read.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" cannot be installed. It is not compatible with this version of the server.", + "__language_name__" : "English (British English)", + "This is an automatically sent email, please do not reply." : "This is an automatically sent email, please do not reply.", + "Help" : "Help", + "Apps" : "Apps", + "Settings" : "Settings", + "Log out" : "Log out", + "Users" : "Users", + "Unknown user" : "Unknown user", + "Additional settings" : "Additional settings", + "%s enter the database username and name." : "%s enter the database username and name.", + "%s enter the database username." : "%s enter the database username.", + "%s enter the database name." : "%s enter the database name.", + "%s you may not use dots in the database name" : "%s you may not use dots in the database name", + "You need to enter details of an existing account." : "You need to enter details of an existing account.", + "Oracle connection could not be established" : "Oracle connection could not be established", + "Oracle username and/or password not valid" : "Oracle username and/or password not valid", + "PostgreSQL username and/or password not valid" : "PostgreSQL username and/or password not valid", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! ", + "For the best results, please consider using a GNU/Linux server instead." : "For the best results, please consider using a GNU/Linux server instead.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir setting has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.", + "Set an admin username." : "Set an admin username.", + "Set an admin password." : "Set an admin password.", + "Can't create or write into the data directory %s" : "Can't create or write into the data directory %s", + "Invalid Federated Cloud ID" : "Invalid Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Sharing backend %s must implement the interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Sharing backend %s not found", + "Sharing backend for %s not found" : "Sharing backend for %s not found", + "Open »%s«" : "Open »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "You are not allowed to share %s", + "Can’t increase permissions of %s" : "Can’t increase permissions of %s", + "Files can’t be shared with delete permissions" : "Files can’t be shared with delete permissions", + "Files can’t be shared with create permissions" : "Files can’t be shared with create permissions", + "Expiration date is in the past" : "Expiration date is in the past", + "Can’t set expiration date more than %s days in the future" : "Can’t set expiration date more than %s days in the future", + "Click the button below to open it." : "Click the button below to open it.", + "The requested share does not exist anymore" : "The requested share does not exist anymore", + "Could not find category \"%s\"" : "Could not find category \"%s\"", + "Sunday" : "Sunday", + "Monday" : "Monday", + "Tuesday" : "Tuesday", + "Wednesday" : "Wednesday", + "Thursday" : "Thursday", + "Friday" : "Friday", + "Saturday" : "Saturday", + "Sun." : "Sun.", + "Mon." : "Mon.", + "Tue." : "Tue.", + "Wed." : "Wed.", + "Thu." : "Thu.", + "Fri." : "Fri.", + "Sat." : "Sat.", + "Su" : "Su", + "Mo" : "Mo", + "Tu" : "Tu", + "We" : "We", + "Th" : "Th", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "January", + "February" : "February", + "March" : "March", + "April" : "April", + "May" : "May", + "June" : "June", + "July" : "July", + "August" : "August", + "September" : "September", + "October" : "October", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "A valid username must be provided", + "Username contains whitespace at the beginning or at the end" : "Username contains whitespace at the beginning or at the end", + "Username must not consist of dots only" : "Username must not consist of dots only", + "A valid password must be provided" : "A valid password must be provided", + "The username is already being used" : "The username is already being used", + "Could not create user" : "Could not create user", + "User disabled" : "User disabled", + "Login canceled by app" : "Login cancelled by app", + "a safe home for all your data" : "a safe home for all your data", + "File is currently busy, please try again later" : "File is currently busy, please try again later", + "Can't read file" : "Can't read file", + "Application is not enabled" : "Application is not enabled", + "Authentication error" : "Authentication error", + "Token expired. Please reload page." : "Token expired. Please reload page.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No database drivers (sqlite, mysql, or postgresql) installed.", + "Cannot write into \"config\" directory" : "Cannot write into \"config\" directory", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "This can usually be fixed by giving the webserver write access to the config directory. See %s", + "Cannot write into \"apps\" directory" : "Cannot write into \"apps\" directory", + "Cannot create \"data\" directory" : "Cannot create \"data\" directory", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "This can usually be fixed by giving the webserver write access to the root directory. See %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.", + "Setting locale to %s failed" : "Setting locale to %s failed", + "Please install one of these locales on your system and restart your webserver." : "Please install one of these locales on your system and restart your webserver.", + "PHP module %s not installed." : "PHP module %s not installed.", + "Please ask your server administrator to install the module." : "Please ask your server administrator to install the module.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP setting \"%s\" is not set to \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Adjusting this setting in php.ini will allow Nextcloud to run", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "To fix this issue set mbstring.func_overload to 0 in your php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 is at least required. Currently %s is installed.", + "To fix this issue update your libxml2 version and restart your web server." : "To fix this issue update your libxml2 version and restart your web server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modules have been installed, but they are still listed as missing?", + "Please ask your server administrator to restart the web server." : "Please ask your server administrator to restart the web server.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 required", + "Please upgrade your database version" : "Please upgrade your database version", + "Your data directory is readable by other users" : "Your data directory is readable by other users", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Please change the permissions to 0770 so that the directory cannot be listed by other users.", + "Your data directory must be an absolute path" : "Your data directory must be an absolute path", + "Check the value of \"datadirectory\" in your configuration" : "Check the value of \"datadirectory\" in your configuration", + "Your data directory is invalid" : "Your data directory is invalid", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ensure there is a file called \".ocdata\" in the root of the data directory.", + "Could not obtain lock type %d on \"%s\"." : "Could not obtain lock type %d on \"%s\".", + "Storage unauthorized. %s" : "Storage unauthorised. %s", + "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s", + "Storage connection error. %s" : "Storage connection error. %s", + "Storage is temporarily not available" : "Storage is temporarily not available", + "Storage connection timeout. %s" : "Storage connection timeout. %s", + "Following databases are supported: %s" : "Following databases are supported: %s", + "Following platforms are supported: %s" : "Following platforms are supported: %s", + "Overview" : "Overview", + "Basic settings" : "Basic settings", + "Sharing" : "Sharing", + "Security" : "Security", + "Personal info" : "Personal info", + "Mobile & desktop" : "Mobile & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/en_GB.json b/docker/overlays/nextcloud/html/lib/l10n/en_GB.json new file mode 100644 index 0000000..4a1af02 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/en_GB.json @@ -0,0 +1,198 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Cannot write into \"config\" directory!", + "This can usually be fixed by giving the webserver write access to the config directory" : "This can usually be fixed by giving the webserver write access to the config directory", + "See %s" : "See %s", + "Sample configuration detected" : "Sample configuration detected", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php", + "%1$s and %2$s" : "%1$s and %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s and %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s and %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s and %5$s", + "Education Edition" : "Education Edition", + "Enterprise bundle" : "Enterprise bundle", + "Groupware bundle" : "Groupware bundle", + "Social sharing bundle" : "Social sharing bundle", + "PHP %s or higher is required." : "PHP %s or higher is required.", + "PHP with a version lower than %s is required." : "PHP with a version lower than %s is required.", + "%sbit or higher PHP required." : "%sbit or higher PHP required.", + "The command line tool %s could not be found" : "The command line tool %s could not be found", + "The library %s is not available." : "The library %s is not available.", + "Server version %s or higher is required." : "Server version %s or higher is required.", + "Server version %s or lower is required." : "Server version %s or lower is required.", + "Logged in user must be an admin" : "Logged in user must be an admin", + "Authentication" : "Authentication", + "Unknown filetype" : "Unknown filetype", + "Invalid image" : "Invalid image", + "Avatar image is not square" : "Avatar image is not square", + "today" : "today", + "tomorrow" : "tomorrow", + "yesterday" : "yesterday", + "_in %n day_::_in %n days_" : ["in %n day","in %n days"], + "_%n day ago_::_%n days ago_" : ["%n day ago","%n days ago"], + "next month" : "next month", + "last month" : "last month", + "_in %n month_::_in %n months_" : ["in %n month","in %n months"], + "_%n month ago_::_%n months ago_" : ["%n month ago","%n months ago"], + "next year" : "next year", + "last year" : "last year", + "_in %n year_::_in %n years_" : ["in %n year","in %n years"], + "_%n year ago_::_%n years ago_" : ["%n year ago","%n years ago"], + "_in %n hour_::_in %n hours_" : ["in %n hour","in %n hours"], + "_%n hour ago_::_%n hours ago_" : ["%n hour ago","%n hours ago"], + "_in %n minute_::_in %n minutes_" : ["in %n minute","in %n minutes"], + "_%n minute ago_::_%n minutes ago_" : ["%n minute ago","%n minutes ago"], + "in a few seconds" : "in a few seconds", + "seconds ago" : "seconds ago", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.", + "File name is a reserved word" : "File name is a reserved word", + "File name contains at least one invalid character" : "File name contains at least one invalid character", + "File name is too long" : "File name is too long", + "Dot files are not allowed" : "Dot files are not allowed", + "Empty filename is not allowed" : "Empty filename is not allowed", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" cannot be installed because appinfo file cannot be read.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" cannot be installed. It is not compatible with this version of the server.", + "__language_name__" : "English (British English)", + "This is an automatically sent email, please do not reply." : "This is an automatically sent email, please do not reply.", + "Help" : "Help", + "Apps" : "Apps", + "Settings" : "Settings", + "Log out" : "Log out", + "Users" : "Users", + "Unknown user" : "Unknown user", + "Additional settings" : "Additional settings", + "%s enter the database username and name." : "%s enter the database username and name.", + "%s enter the database username." : "%s enter the database username.", + "%s enter the database name." : "%s enter the database name.", + "%s you may not use dots in the database name" : "%s you may not use dots in the database name", + "You need to enter details of an existing account." : "You need to enter details of an existing account.", + "Oracle connection could not be established" : "Oracle connection could not be established", + "Oracle username and/or password not valid" : "Oracle username and/or password not valid", + "PostgreSQL username and/or password not valid" : "PostgreSQL username and/or password not valid", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! ", + "For the best results, please consider using a GNU/Linux server instead." : "For the best results, please consider using a GNU/Linux server instead.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir setting has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.", + "Set an admin username." : "Set an admin username.", + "Set an admin password." : "Set an admin password.", + "Can't create or write into the data directory %s" : "Can't create or write into the data directory %s", + "Invalid Federated Cloud ID" : "Invalid Federated Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Sharing backend %s must implement the interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Sharing backend %s not found", + "Sharing backend for %s not found" : "Sharing backend for %s not found", + "Open »%s«" : "Open »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "You are not allowed to share %s", + "Can’t increase permissions of %s" : "Can’t increase permissions of %s", + "Files can’t be shared with delete permissions" : "Files can’t be shared with delete permissions", + "Files can’t be shared with create permissions" : "Files can’t be shared with create permissions", + "Expiration date is in the past" : "Expiration date is in the past", + "Can’t set expiration date more than %s days in the future" : "Can’t set expiration date more than %s days in the future", + "Click the button below to open it." : "Click the button below to open it.", + "The requested share does not exist anymore" : "The requested share does not exist anymore", + "Could not find category \"%s\"" : "Could not find category \"%s\"", + "Sunday" : "Sunday", + "Monday" : "Monday", + "Tuesday" : "Tuesday", + "Wednesday" : "Wednesday", + "Thursday" : "Thursday", + "Friday" : "Friday", + "Saturday" : "Saturday", + "Sun." : "Sun.", + "Mon." : "Mon.", + "Tue." : "Tue.", + "Wed." : "Wed.", + "Thu." : "Thu.", + "Fri." : "Fri.", + "Sat." : "Sat.", + "Su" : "Su", + "Mo" : "Mo", + "Tu" : "Tu", + "We" : "We", + "Th" : "Th", + "Fr" : "Fr", + "Sa" : "Sa", + "January" : "January", + "February" : "February", + "March" : "March", + "April" : "April", + "May" : "May", + "June" : "June", + "July" : "July", + "August" : "August", + "September" : "September", + "October" : "October", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "A valid username must be provided", + "Username contains whitespace at the beginning or at the end" : "Username contains whitespace at the beginning or at the end", + "Username must not consist of dots only" : "Username must not consist of dots only", + "A valid password must be provided" : "A valid password must be provided", + "The username is already being used" : "The username is already being used", + "Could not create user" : "Could not create user", + "User disabled" : "User disabled", + "Login canceled by app" : "Login cancelled by app", + "a safe home for all your data" : "a safe home for all your data", + "File is currently busy, please try again later" : "File is currently busy, please try again later", + "Can't read file" : "Can't read file", + "Application is not enabled" : "Application is not enabled", + "Authentication error" : "Authentication error", + "Token expired. Please reload page." : "Token expired. Please reload page.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No database drivers (sqlite, mysql, or postgresql) installed.", + "Cannot write into \"config\" directory" : "Cannot write into \"config\" directory", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "This can usually be fixed by giving the webserver write access to the config directory. See %s", + "Cannot write into \"apps\" directory" : "Cannot write into \"apps\" directory", + "Cannot create \"data\" directory" : "Cannot create \"data\" directory", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "This can usually be fixed by giving the webserver write access to the root directory. See %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.", + "Setting locale to %s failed" : "Setting locale to %s failed", + "Please install one of these locales on your system and restart your webserver." : "Please install one of these locales on your system and restart your webserver.", + "PHP module %s not installed." : "PHP module %s not installed.", + "Please ask your server administrator to install the module." : "Please ask your server administrator to install the module.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP setting \"%s\" is not set to \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Adjusting this setting in php.ini will allow Nextcloud to run", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "To fix this issue set mbstring.func_overload to 0 in your php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 is at least required. Currently %s is installed.", + "To fix this issue update your libxml2 version and restart your web server." : "To fix this issue update your libxml2 version and restart your web server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modules have been installed, but they are still listed as missing?", + "Please ask your server administrator to restart the web server." : "Please ask your server administrator to restart the web server.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 required", + "Please upgrade your database version" : "Please upgrade your database version", + "Your data directory is readable by other users" : "Your data directory is readable by other users", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Please change the permissions to 0770 so that the directory cannot be listed by other users.", + "Your data directory must be an absolute path" : "Your data directory must be an absolute path", + "Check the value of \"datadirectory\" in your configuration" : "Check the value of \"datadirectory\" in your configuration", + "Your data directory is invalid" : "Your data directory is invalid", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ensure there is a file called \".ocdata\" in the root of the data directory.", + "Could not obtain lock type %d on \"%s\"." : "Could not obtain lock type %d on \"%s\".", + "Storage unauthorized. %s" : "Storage unauthorised. %s", + "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s", + "Storage connection error. %s" : "Storage connection error. %s", + "Storage is temporarily not available" : "Storage is temporarily not available", + "Storage connection timeout. %s" : "Storage connection timeout. %s", + "Following databases are supported: %s" : "Following databases are supported: %s", + "Following platforms are supported: %s" : "Following platforms are supported: %s", + "Overview" : "Overview", + "Basic settings" : "Basic settings", + "Sharing" : "Sharing", + "Security" : "Security", + "Personal info" : "Personal info", + "Mobile & desktop" : "Mobile & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/eo.js b/docker/overlays/nextcloud/html/lib/l10n/eo.js new file mode 100644 index 0000000..d9a6230 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/eo.js @@ -0,0 +1,230 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Ne skribeblas la dosierujo „config“!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Igi la agordodosierujon alirebla de la servilo kutime solvas tiun problemon.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Aŭ, se vi preferas lasi la dosieron config.php nurlega, metu la opcion „config_is_read_only“ al vera („true“) ene de ĝi.", + "See %s" : "Vidi %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Aŭ, se vi preferas lasi la dosieron config.php nurlega, metu la opcion „config_is_read_only“ al vera („true“) ene de ĝi. Vidu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "La dosieroj de la aplikaĵo %1$s ne estis bone anstataŭigitaj. Certigu, ke tiu aplikaĵa versio kongruas kun la servilo.", + "Sample configuration detected" : "Ekzempla agordo trovita", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Ekzempla agordo estis kopiita en via sistemo. Tio povas paneigi vian instalaĵon, kaj ne estas subtenata. Bv. legi la dokumentaron antaŭ ol fari ŝanĝojn en config.php", + "%1$s and %2$s" : "%1$s kaj %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s kaj %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s kaj %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s kaj %5$s", + "Education Edition" : "Eldono por edukado", + "Enterprise bundle" : "Aplikaĵa kuniĝo por firmao", + "Groupware bundle" : "Aplikaĵa kuniĝo por grupa kunlaborado", + "Social sharing bundle" : "Aplikaĵa kuniĝo por socia kuhavigo", + "PHP %s or higher is required." : "PHP %s aŭ pli alta necesas.", + "PHP with a version lower than %s is required." : "Necesas pli malalta eldono de PHP ol %s.", + "%sbit or higher PHP required." : "PHP je %sbitoj aŭ pli alta necesas.", + "The command line tool %s could not be found" : "La komandlinia ilo %s ne troviĝis", + "The library %s is not available." : "La biblioteko %s ne haveblas.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Biblioteko %1$s kun versio pli ol %2$s bezoniĝas. Nuna versio estas %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Biblioteko %1$s kun versio malpli ol %2$s bezoniĝas. Nuna versio estas %3$s.", + "Server version %s or higher is required." : "Servilo kun versio %s aŭ pli bezoniĝas.", + "Server version %s or lower is required." : "Servilo kun versio %s aŭ malpli bezoniĝas.", + "Logged in user must be an admin or sub admin" : "La ensalutanta uzanto estu administranto aŭ subadministranto", + "Logged in user must be an admin" : "La ensalutanta uzanto estu administranto", + "Wiping of device %s has started" : "Forviŝado de la aparato %s komencis", + "Wiping of device »%s« has started" : "Forviŝado de la aparato „%s“ komencis", + "»%s« started remote wipe" : "„%s“ komencis foran forviŝadon", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "La aparato aŭ aplikaĵo „%s“ komencis la taskon de fora forviŝado. Vi ricevos plian retmesaĝon, kiam la tasko finiĝos", + "Wiping of device %s has finished" : "La forviŝado de la aparato %s finiĝis", + "Wiping of device »%s« has finished" : "La forviŝado de la aparato „%s“ finiĝis", + "»%s« finished remote wipe" : "„%s“ finis la foran forviŝadon", + "Device or application »%s« has finished the remote wipe process." : "La aparato aŭ aplikaĵo „%s“ finis la taskon de fora forviŝado.", + "Remote wipe started" : "Defora forviŝado komenciĝis", + "A remote wipe was started on device %s" : "Defora forviŝado komenciĝis ĉe aparato %s", + "Remote wipe finished" : "Defora forviŝado finis", + "The remote wipe on %s has finished" : "La defora forviŝado ĉe %s finis", + "Authentication" : "Aŭtentigo", + "Unknown filetype" : "Nekonata dosiertipo", + "Invalid image" : "Nevalida bildo", + "Avatar image is not square" : "Avatarbildo ne estas kvadrata", + "today" : "hodiaŭ", + "tomorrow" : "morgaŭ", + "yesterday" : "hieraŭ", + "_in %n day_::_in %n days_" : ["post %n tago","post %n tagoj"], + "_%n day ago_::_%n days ago_" : ["antaŭ %n tago","antaŭ %n tagoj"], + "next month" : "venontmonate", + "last month" : "lastmonate", + "_in %n month_::_in %n months_" : ["post %n monato","post %n monatoj"], + "_%n month ago_::_%n months ago_" : ["antaŭ %n monato","antaŭ %n monatoj"], + "next year" : "venontjare", + "last year" : "lastjare", + "_in %n year_::_in %n years_" : ["post %n jaro","post %n jaroj"], + "_%n year ago_::_%n years ago_" : ["antaŭ %n jaro","antaŭ %n jaroj"], + "_in %n hour_::_in %n hours_" : ["post %n horo","post %n horoj"], + "_%n hour ago_::_%n hours ago_" : ["antaŭ %n horo","antaŭ %n horoj"], + "_in %n minute_::_in %n minutes_" : ["post %n minuto","post %n minutoj"], + "_%n minute ago_::_%n minutes ago_" : ["antaŭ %n minuto","antaŭ %n minutoj"], + "in a few seconds" : "post kelkaj sekundoj", + "seconds ago" : "antaŭ kelkaj sekundoj", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulo kun identigilo %s ne ekzistas. Bv. ŝalti ĝin en la aplikaĵa agordo aŭ kontakti vian administranton.", + "File name is a reserved word" : "Dosiernomo estas rezervita vorto", + "File name contains at least one invalid character" : "Dosiernomo enhavas almenaŭ unu nevalidan signon", + "File name is too long" : "La dosiernomo tro longas", + "Dot files are not allowed" : "Dosiernomo, kiu komenciĝas per punkto, ne estas permesata", + "Empty filename is not allowed" : "Malplena dosiernomo ne estas permesata", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplikaĵo „%s“ ne instaleblas, ĉar ties dosiero „appinfo“ ne legeblis.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplikaĵo „%s“ ne instaleblas, ĉar ĝi ne kongruas kun tiu servila versio.", + "__language_name__" : "Esperanto", + "This is an automatically sent email, please do not reply." : "Tio estas aŭtomate sendita retpoŝtmesaĝo; bv. ne respondi.", + "Help" : "Helpo", + "Apps" : "Aplikaĵoj", + "Settings" : "Agordo", + "Log out" : "Elsaluti", + "Users" : "Uzantoj", + "Unknown user" : "Nekonata uzanto", + "Additional settings" : "Plia agordo", + "%s enter the database username and name." : "%s entajpu la nomon kaj la uzantnomon de la datumbazo.", + "%s enter the database username." : "%s entajpu la datumbazan uzantnomon.", + "%s enter the database name." : "%s entajpu la datumbazan nomon.", + "%s you may not use dots in the database name" : "%s vi ne povas uzi punktojn en la nomo de la datumbazo", + "You need to enter details of an existing account." : "Vi entajpu detalojn pri ekzistanta konto.", + "Oracle connection could not be established" : "Konekto al Oracle ne povis stariĝi", + "Oracle username and/or password not valid" : "La uzantnomo aŭ la pasvorto de Oracle ne validas", + "PostgreSQL username and/or password not valid" : "La uzantnomo aŭ la pasvorto de PostgreSQL ne validas", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "MacOS X ne estas subtenata kaj %s ne bone funkcios ĉe ĝi. Uzu ĝin je via risko!", + "For the best results, please consider using a GNU/Linux server instead." : "Por pli bona funkciado, bv. pripensi uzi GNU-Linuksan servilon anstataŭe.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Ŝajnas, ke tiu servilo %s uzas 32-bitan PHP-version, kaj ke la agordo „open_basedir“ ekzistas. Tio kaŭzos problemojn pri dosieroj pli grandaj ol 4 GB, kaj do estas tre malrekomendita.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bv. forigi la agordon „open_basedir“ de via php.ini, aŭ uzu 64-bitan version de PHP.", + "Set an admin username." : "Agordi uzantnomon de administranto.", + "Set an admin password." : "Agordi pasvorton de administranto.", + "Can't create or write into the data directory %s" : "Ne kreeblas aŭ ne skribeblas la datumdosierujo %s", + "Invalid Federated Cloud ID" : "Nevalida federnuba identigilo", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Kunhava interna servo %s devas realigi la interfacon „OCP\\Share_Backend“", + "Sharing backend %s not found" : "Kunhava interna servo %s ne troviĝas", + "Sharing backend for %s not found" : "Kunhava interna servo por %s ne troviĝas", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s kunhavigis „%2$s“ kun vi kaj volas aldoni:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s kunhavigis „%2$s“ kun vi kaj volas aldoni", + "»%s« added a note to a file shared with you" : "„%s“ aldonis noton al dosiero kunhavigita kun vi", + "Open »%s«" : "Malfermi „%s“", + "%1$s via %2$s" : "%1$s pere de %2$s", + "You are not allowed to share %s" : "Vi ne permesatas kunhavigi %s", + "Can’t increase permissions of %s" : "Ne eblas pliigi permesojn de %s", + "Files can’t be shared with delete permissions" : "Dosieroj ne estas kunhavigitaj kun permesoj de forigo", + "Files can’t be shared with create permissions" : "Dosieroj ne estas kunhavigitaj kun permesoj de kreado", + "Expiration date is in the past" : "Limdato troviĝas en la estinteco", + "Can’t set expiration date more than %s days in the future" : "Ne eblas agordi limdaton pli ol %s tagojn en la estonteco", + "%1$s shared »%2$s« with you" : "%1$s kunhavigis „%2$s“ kun vi", + "%1$s shared »%2$s« with you." : "%1$s kunhavigis „%2$s“ kun vi.", + "Click the button below to open it." : "Alklaku la butonon ĉi-sube por malfermi ĝin.", + "The requested share does not exist anymore" : "La petita kunhavo ne plu ekzistas", + "Could not find category \"%s\"" : "Ne troviĝis kategorio „%s“", + "Sunday" : "dimanĉo", + "Monday" : "lundo", + "Tuesday" : "mardo", + "Wednesday" : "merkredo", + "Thursday" : "ĵaŭdo", + "Friday" : "vendredo", + "Saturday" : "sabato", + "Sun." : "dim.", + "Mon." : "lun.", + "Tue." : "mar.", + "Wed." : "mer.", + "Thu." : "ĵaŭ.", + "Fri." : "ven.", + "Sat." : "sab.", + "Su" : "Di", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Ĵa", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Januaro", + "February" : "Februaro", + "March" : "Marto", + "April" : "Aprilo", + "May" : "Majo", + "June" : "Junio", + "July" : "Julio", + "August" : "Aŭgusto", + "September" : "Septembro", + "October" : "Oktobro", + "November" : "Novembro", + "December" : "Decembro", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aŭg.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur la jenaj signoj eblas en uzantnomo: „a“ ĝis „z“, „A“ ĝis „Z“, „0“ ĝis „9“ kaj „_“ (substreko), „@“, „-“ (streketo), „'“ (apostrofo) kaj „.“ (punkto)", + "A valid username must be provided" : "Valida uzantnomo devas esti provizita", + "Username contains whitespace at the beginning or at the end" : "Uzantnomo enhavas spaceton ĉe la komenco aŭ la fino", + "Username must not consist of dots only" : "Uzantnomo ne povas enhavi nur punktojn", + "A valid password must be provided" : "Valida pasvorto devas esti provizita", + "The username is already being used" : "La uzantnomo jam uziĝas", + "Could not create user" : "Ne povis krei uzanton", + "User disabled" : "Uzanto malebligita", + "Login canceled by app" : "Ensaluto estis nuligita de aplikaĵo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "La aplikaĵo „%1$s“ ne instaliĝas, ĉar la jenaj dependecoj ne plenumiĝas: %2$s", + "a safe home for all your data" : "sekura hejmo por ĉiuj viaj datumoj", + "File is currently busy, please try again later" : "La dosiero estas nun okupita, bv. reprovi poste", + "Can't read file" : "Ne legeblas dosiero", + "Application is not enabled" : "La aplikaĵo ne estas ŝaltita", + "Authentication error" : "Aŭtentiga eraro", + "Token expired. Please reload page." : "Ĵetono eksvalidiĝis. Bonvolu reŝargi la paĝon.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Neniu datumbaza pelilo (sqlite, mysql, or postgresql) instalita.", + "Cannot write into \"config\" directory" : "Ne skribeblas la dosierujo „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Igi la agordodosierujon alirebla de la servilo kutime solvas tiun problemon. Vidu %s", + "Cannot write into \"apps\" directory" : "Ne skribeblas la dosierujo „apps“", + "Cannot create \"data\" directory" : "Ne kreeblas la dosierujo „data“", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Igi la radikan dosierujon alirebla de la servilo kutime solvas tiun problemon. Vidu %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Igi la radikan dosierujon skribebla de la servilo kutime solvas tiun problemon. Vidu %s.", + "Setting locale to %s failed" : "Agordi la lokaĵaron al %s malsukcesis", + "Please install one of these locales on your system and restart your webserver." : "Bv. instali unu el tiu lokaĵaro ĉe via sistemo, kaj restartigu la TTT-servilon.", + "PHP module %s not installed." : "PHP-modulo %s ne instalita.", + "Please ask your server administrator to install the module." : "Bonvolu peti vian sistemadministranton instali la modulon.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-agordo „%s“ ne egalas al „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Modifo de tiu agordo en „php.ini“ funkciigas Nextcloud-on denove.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload egalas al „%s“ anstataŭ al la atendata „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Por ripari tiun problemon, agordu mbstring.func_overload al 0 en via dosiero „php.ini“", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 almenaŭ necesas. Nun %s estas instalita.", + "To fix this issue update your libxml2 version and restart your web server." : "Por ripari tiun problemon, ĝisdatigu vian version de libxml2, kaj restartigu la TTT-servilon.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ŝajne estas agordita por senigi la entekstajn dokumentarojn. Tio malfunkciigos plurajn kernajn aplikaĵojn.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tion kaŭzas probable kaŝilo aŭ plirapidigilo kiel „Zend OPcache“ aŭ „eAccelerator“.", + "PHP modules have been installed, but they are still listed as missing?" : "Ĉu PHP-moduloj estas instalitaj, sed ĉiam montritaj kiel mankantaj?", + "Please ask your server administrator to restart the web server." : "Bonvolu peti vian serviladministranton, ke ŝi aŭ li restartigu la TTT-servilon.", + "PostgreSQL >= 9 required" : "PostgreSQL ⩾ 9 necesas", + "Please upgrade your database version" : "Bonvolu ĝisdatigi la version de via datumbazo", + "Your data directory is readable by other users" : "Via dosierujo de datumoj legeblas de aliaj uzantoj", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bv. ŝanĝi la permesojn al 0770, tiel la dosierujo ne listigeblas de aliaj uzantoj.", + "Your data directory must be an absolute path" : "Via dosierujo de datumoj estu absoluta vojo", + "Check the value of \"datadirectory\" in your configuration" : "Kontrolu la valoron de „datadirectory“ en via agordo", + "Your data directory is invalid" : "Via dosierujo de datumoj ne validas", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Certigu, ke estas dosiero nomata „.ocdata“ en la radiko de la dosierujo de datumoj.", + "Action \"%s\" not supported or implemented." : "Ago „%s“ ne estas subtenata aŭ realigita.", + "Authentication failed, wrong token or provider ID given" : "Aŭtentigo malsukcesis: neĝusta ĵetono aŭ provizanto-identigilo specifita", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametroj mankas por realigi la peton. Mankantaj parametroj: „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Identigilo „%1$s“ jam uziĝas de federnuba provizanto „%2$s“", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Federnuba provizanto kun identigilo „%s“ ne ekzistas.", + "Could not obtain lock type %d on \"%s\"." : "Ne eblis havi ŝlostipon %d sur „%s“.", + "Storage unauthorized. %s" : "Konservejo ne permesata. %s", + "Storage incomplete configuration. %s" : "Nekompleta agordo de konservejo. %s", + "Storage connection error. %s" : "Konekta eraro al konservejo. %s", + "Storage is temporarily not available" : "Konservejo provizore ne disponeblas", + "Storage connection timeout. %s" : "Konekto al konservejo eltempiĝis. %s", + "Following databases are supported: %s" : "La jenaj datumbazoj estas subtenataj: %s", + "Following platforms are supported: %s" : "La jenaj platformoj estas subtenataj: %s", + "Overview" : "Superrigardo", + "Basic settings" : "Bazaj agordoj", + "Sharing" : "Kunhavigo", + "Security" : "Sekurigo", + "Groupware" : "Grupa kunlaborado", + "Personal info" : "Persona informo", + "Mobile & desktop" : "Porteblaj kaj labortablaj aplikaĵoj", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Igi la „apps“ dosierujon alirebla de la servilo aŭ malŝalti la aplikaĵejon en la agordodosiero kutime solvas tiun problemon. Vidu %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/eo.json b/docker/overlays/nextcloud/html/lib/l10n/eo.json new file mode 100644 index 0000000..36fc121 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/eo.json @@ -0,0 +1,228 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Ne skribeblas la dosierujo „config“!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Igi la agordodosierujon alirebla de la servilo kutime solvas tiun problemon.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Aŭ, se vi preferas lasi la dosieron config.php nurlega, metu la opcion „config_is_read_only“ al vera („true“) ene de ĝi.", + "See %s" : "Vidi %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Aŭ, se vi preferas lasi la dosieron config.php nurlega, metu la opcion „config_is_read_only“ al vera („true“) ene de ĝi. Vidu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "La dosieroj de la aplikaĵo %1$s ne estis bone anstataŭigitaj. Certigu, ke tiu aplikaĵa versio kongruas kun la servilo.", + "Sample configuration detected" : "Ekzempla agordo trovita", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Ekzempla agordo estis kopiita en via sistemo. Tio povas paneigi vian instalaĵon, kaj ne estas subtenata. Bv. legi la dokumentaron antaŭ ol fari ŝanĝojn en config.php", + "%1$s and %2$s" : "%1$s kaj %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s kaj %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s kaj %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s kaj %5$s", + "Education Edition" : "Eldono por edukado", + "Enterprise bundle" : "Aplikaĵa kuniĝo por firmao", + "Groupware bundle" : "Aplikaĵa kuniĝo por grupa kunlaborado", + "Social sharing bundle" : "Aplikaĵa kuniĝo por socia kuhavigo", + "PHP %s or higher is required." : "PHP %s aŭ pli alta necesas.", + "PHP with a version lower than %s is required." : "Necesas pli malalta eldono de PHP ol %s.", + "%sbit or higher PHP required." : "PHP je %sbitoj aŭ pli alta necesas.", + "The command line tool %s could not be found" : "La komandlinia ilo %s ne troviĝis", + "The library %s is not available." : "La biblioteko %s ne haveblas.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Biblioteko %1$s kun versio pli ol %2$s bezoniĝas. Nuna versio estas %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Biblioteko %1$s kun versio malpli ol %2$s bezoniĝas. Nuna versio estas %3$s.", + "Server version %s or higher is required." : "Servilo kun versio %s aŭ pli bezoniĝas.", + "Server version %s or lower is required." : "Servilo kun versio %s aŭ malpli bezoniĝas.", + "Logged in user must be an admin or sub admin" : "La ensalutanta uzanto estu administranto aŭ subadministranto", + "Logged in user must be an admin" : "La ensalutanta uzanto estu administranto", + "Wiping of device %s has started" : "Forviŝado de la aparato %s komencis", + "Wiping of device »%s« has started" : "Forviŝado de la aparato „%s“ komencis", + "»%s« started remote wipe" : "„%s“ komencis foran forviŝadon", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "La aparato aŭ aplikaĵo „%s“ komencis la taskon de fora forviŝado. Vi ricevos plian retmesaĝon, kiam la tasko finiĝos", + "Wiping of device %s has finished" : "La forviŝado de la aparato %s finiĝis", + "Wiping of device »%s« has finished" : "La forviŝado de la aparato „%s“ finiĝis", + "»%s« finished remote wipe" : "„%s“ finis la foran forviŝadon", + "Device or application »%s« has finished the remote wipe process." : "La aparato aŭ aplikaĵo „%s“ finis la taskon de fora forviŝado.", + "Remote wipe started" : "Defora forviŝado komenciĝis", + "A remote wipe was started on device %s" : "Defora forviŝado komenciĝis ĉe aparato %s", + "Remote wipe finished" : "Defora forviŝado finis", + "The remote wipe on %s has finished" : "La defora forviŝado ĉe %s finis", + "Authentication" : "Aŭtentigo", + "Unknown filetype" : "Nekonata dosiertipo", + "Invalid image" : "Nevalida bildo", + "Avatar image is not square" : "Avatarbildo ne estas kvadrata", + "today" : "hodiaŭ", + "tomorrow" : "morgaŭ", + "yesterday" : "hieraŭ", + "_in %n day_::_in %n days_" : ["post %n tago","post %n tagoj"], + "_%n day ago_::_%n days ago_" : ["antaŭ %n tago","antaŭ %n tagoj"], + "next month" : "venontmonate", + "last month" : "lastmonate", + "_in %n month_::_in %n months_" : ["post %n monato","post %n monatoj"], + "_%n month ago_::_%n months ago_" : ["antaŭ %n monato","antaŭ %n monatoj"], + "next year" : "venontjare", + "last year" : "lastjare", + "_in %n year_::_in %n years_" : ["post %n jaro","post %n jaroj"], + "_%n year ago_::_%n years ago_" : ["antaŭ %n jaro","antaŭ %n jaroj"], + "_in %n hour_::_in %n hours_" : ["post %n horo","post %n horoj"], + "_%n hour ago_::_%n hours ago_" : ["antaŭ %n horo","antaŭ %n horoj"], + "_in %n minute_::_in %n minutes_" : ["post %n minuto","post %n minutoj"], + "_%n minute ago_::_%n minutes ago_" : ["antaŭ %n minuto","antaŭ %n minutoj"], + "in a few seconds" : "post kelkaj sekundoj", + "seconds ago" : "antaŭ kelkaj sekundoj", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulo kun identigilo %s ne ekzistas. Bv. ŝalti ĝin en la aplikaĵa agordo aŭ kontakti vian administranton.", + "File name is a reserved word" : "Dosiernomo estas rezervita vorto", + "File name contains at least one invalid character" : "Dosiernomo enhavas almenaŭ unu nevalidan signon", + "File name is too long" : "La dosiernomo tro longas", + "Dot files are not allowed" : "Dosiernomo, kiu komenciĝas per punkto, ne estas permesata", + "Empty filename is not allowed" : "Malplena dosiernomo ne estas permesata", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplikaĵo „%s“ ne instaleblas, ĉar ties dosiero „appinfo“ ne legeblis.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplikaĵo „%s“ ne instaleblas, ĉar ĝi ne kongruas kun tiu servila versio.", + "__language_name__" : "Esperanto", + "This is an automatically sent email, please do not reply." : "Tio estas aŭtomate sendita retpoŝtmesaĝo; bv. ne respondi.", + "Help" : "Helpo", + "Apps" : "Aplikaĵoj", + "Settings" : "Agordo", + "Log out" : "Elsaluti", + "Users" : "Uzantoj", + "Unknown user" : "Nekonata uzanto", + "Additional settings" : "Plia agordo", + "%s enter the database username and name." : "%s entajpu la nomon kaj la uzantnomon de la datumbazo.", + "%s enter the database username." : "%s entajpu la datumbazan uzantnomon.", + "%s enter the database name." : "%s entajpu la datumbazan nomon.", + "%s you may not use dots in the database name" : "%s vi ne povas uzi punktojn en la nomo de la datumbazo", + "You need to enter details of an existing account." : "Vi entajpu detalojn pri ekzistanta konto.", + "Oracle connection could not be established" : "Konekto al Oracle ne povis stariĝi", + "Oracle username and/or password not valid" : "La uzantnomo aŭ la pasvorto de Oracle ne validas", + "PostgreSQL username and/or password not valid" : "La uzantnomo aŭ la pasvorto de PostgreSQL ne validas", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "MacOS X ne estas subtenata kaj %s ne bone funkcios ĉe ĝi. Uzu ĝin je via risko!", + "For the best results, please consider using a GNU/Linux server instead." : "Por pli bona funkciado, bv. pripensi uzi GNU-Linuksan servilon anstataŭe.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Ŝajnas, ke tiu servilo %s uzas 32-bitan PHP-version, kaj ke la agordo „open_basedir“ ekzistas. Tio kaŭzos problemojn pri dosieroj pli grandaj ol 4 GB, kaj do estas tre malrekomendita.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Bv. forigi la agordon „open_basedir“ de via php.ini, aŭ uzu 64-bitan version de PHP.", + "Set an admin username." : "Agordi uzantnomon de administranto.", + "Set an admin password." : "Agordi pasvorton de administranto.", + "Can't create or write into the data directory %s" : "Ne kreeblas aŭ ne skribeblas la datumdosierujo %s", + "Invalid Federated Cloud ID" : "Nevalida federnuba identigilo", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Kunhava interna servo %s devas realigi la interfacon „OCP\\Share_Backend“", + "Sharing backend %s not found" : "Kunhava interna servo %s ne troviĝas", + "Sharing backend for %s not found" : "Kunhava interna servo por %s ne troviĝas", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s kunhavigis „%2$s“ kun vi kaj volas aldoni:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s kunhavigis „%2$s“ kun vi kaj volas aldoni", + "»%s« added a note to a file shared with you" : "„%s“ aldonis noton al dosiero kunhavigita kun vi", + "Open »%s«" : "Malfermi „%s“", + "%1$s via %2$s" : "%1$s pere de %2$s", + "You are not allowed to share %s" : "Vi ne permesatas kunhavigi %s", + "Can’t increase permissions of %s" : "Ne eblas pliigi permesojn de %s", + "Files can’t be shared with delete permissions" : "Dosieroj ne estas kunhavigitaj kun permesoj de forigo", + "Files can’t be shared with create permissions" : "Dosieroj ne estas kunhavigitaj kun permesoj de kreado", + "Expiration date is in the past" : "Limdato troviĝas en la estinteco", + "Can’t set expiration date more than %s days in the future" : "Ne eblas agordi limdaton pli ol %s tagojn en la estonteco", + "%1$s shared »%2$s« with you" : "%1$s kunhavigis „%2$s“ kun vi", + "%1$s shared »%2$s« with you." : "%1$s kunhavigis „%2$s“ kun vi.", + "Click the button below to open it." : "Alklaku la butonon ĉi-sube por malfermi ĝin.", + "The requested share does not exist anymore" : "La petita kunhavo ne plu ekzistas", + "Could not find category \"%s\"" : "Ne troviĝis kategorio „%s“", + "Sunday" : "dimanĉo", + "Monday" : "lundo", + "Tuesday" : "mardo", + "Wednesday" : "merkredo", + "Thursday" : "ĵaŭdo", + "Friday" : "vendredo", + "Saturday" : "sabato", + "Sun." : "dim.", + "Mon." : "lun.", + "Tue." : "mar.", + "Wed." : "mer.", + "Thu." : "ĵaŭ.", + "Fri." : "ven.", + "Sat." : "sab.", + "Su" : "Di", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Ĵa", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Januaro", + "February" : "Februaro", + "March" : "Marto", + "April" : "Aprilo", + "May" : "Majo", + "June" : "Junio", + "July" : "Julio", + "August" : "Aŭgusto", + "September" : "Septembro", + "October" : "Oktobro", + "November" : "Novembro", + "December" : "Decembro", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aŭg.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur la jenaj signoj eblas en uzantnomo: „a“ ĝis „z“, „A“ ĝis „Z“, „0“ ĝis „9“ kaj „_“ (substreko), „@“, „-“ (streketo), „'“ (apostrofo) kaj „.“ (punkto)", + "A valid username must be provided" : "Valida uzantnomo devas esti provizita", + "Username contains whitespace at the beginning or at the end" : "Uzantnomo enhavas spaceton ĉe la komenco aŭ la fino", + "Username must not consist of dots only" : "Uzantnomo ne povas enhavi nur punktojn", + "A valid password must be provided" : "Valida pasvorto devas esti provizita", + "The username is already being used" : "La uzantnomo jam uziĝas", + "Could not create user" : "Ne povis krei uzanton", + "User disabled" : "Uzanto malebligita", + "Login canceled by app" : "Ensaluto estis nuligita de aplikaĵo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "La aplikaĵo „%1$s“ ne instaliĝas, ĉar la jenaj dependecoj ne plenumiĝas: %2$s", + "a safe home for all your data" : "sekura hejmo por ĉiuj viaj datumoj", + "File is currently busy, please try again later" : "La dosiero estas nun okupita, bv. reprovi poste", + "Can't read file" : "Ne legeblas dosiero", + "Application is not enabled" : "La aplikaĵo ne estas ŝaltita", + "Authentication error" : "Aŭtentiga eraro", + "Token expired. Please reload page." : "Ĵetono eksvalidiĝis. Bonvolu reŝargi la paĝon.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Neniu datumbaza pelilo (sqlite, mysql, or postgresql) instalita.", + "Cannot write into \"config\" directory" : "Ne skribeblas la dosierujo „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Igi la agordodosierujon alirebla de la servilo kutime solvas tiun problemon. Vidu %s", + "Cannot write into \"apps\" directory" : "Ne skribeblas la dosierujo „apps“", + "Cannot create \"data\" directory" : "Ne kreeblas la dosierujo „data“", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Igi la radikan dosierujon alirebla de la servilo kutime solvas tiun problemon. Vidu %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Igi la radikan dosierujon skribebla de la servilo kutime solvas tiun problemon. Vidu %s.", + "Setting locale to %s failed" : "Agordi la lokaĵaron al %s malsukcesis", + "Please install one of these locales on your system and restart your webserver." : "Bv. instali unu el tiu lokaĵaro ĉe via sistemo, kaj restartigu la TTT-servilon.", + "PHP module %s not installed." : "PHP-modulo %s ne instalita.", + "Please ask your server administrator to install the module." : "Bonvolu peti vian sistemadministranton instali la modulon.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-agordo „%s“ ne egalas al „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Modifo de tiu agordo en „php.ini“ funkciigas Nextcloud-on denove.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload egalas al „%s“ anstataŭ al la atendata „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Por ripari tiun problemon, agordu mbstring.func_overload al 0 en via dosiero „php.ini“", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 almenaŭ necesas. Nun %s estas instalita.", + "To fix this issue update your libxml2 version and restart your web server." : "Por ripari tiun problemon, ĝisdatigu vian version de libxml2, kaj restartigu la TTT-servilon.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ŝajne estas agordita por senigi la entekstajn dokumentarojn. Tio malfunkciigos plurajn kernajn aplikaĵojn.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tion kaŭzas probable kaŝilo aŭ plirapidigilo kiel „Zend OPcache“ aŭ „eAccelerator“.", + "PHP modules have been installed, but they are still listed as missing?" : "Ĉu PHP-moduloj estas instalitaj, sed ĉiam montritaj kiel mankantaj?", + "Please ask your server administrator to restart the web server." : "Bonvolu peti vian serviladministranton, ke ŝi aŭ li restartigu la TTT-servilon.", + "PostgreSQL >= 9 required" : "PostgreSQL ⩾ 9 necesas", + "Please upgrade your database version" : "Bonvolu ĝisdatigi la version de via datumbazo", + "Your data directory is readable by other users" : "Via dosierujo de datumoj legeblas de aliaj uzantoj", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Bv. ŝanĝi la permesojn al 0770, tiel la dosierujo ne listigeblas de aliaj uzantoj.", + "Your data directory must be an absolute path" : "Via dosierujo de datumoj estu absoluta vojo", + "Check the value of \"datadirectory\" in your configuration" : "Kontrolu la valoron de „datadirectory“ en via agordo", + "Your data directory is invalid" : "Via dosierujo de datumoj ne validas", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Certigu, ke estas dosiero nomata „.ocdata“ en la radiko de la dosierujo de datumoj.", + "Action \"%s\" not supported or implemented." : "Ago „%s“ ne estas subtenata aŭ realigita.", + "Authentication failed, wrong token or provider ID given" : "Aŭtentigo malsukcesis: neĝusta ĵetono aŭ provizanto-identigilo specifita", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametroj mankas por realigi la peton. Mankantaj parametroj: „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Identigilo „%1$s“ jam uziĝas de federnuba provizanto „%2$s“", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Federnuba provizanto kun identigilo „%s“ ne ekzistas.", + "Could not obtain lock type %d on \"%s\"." : "Ne eblis havi ŝlostipon %d sur „%s“.", + "Storage unauthorized. %s" : "Konservejo ne permesata. %s", + "Storage incomplete configuration. %s" : "Nekompleta agordo de konservejo. %s", + "Storage connection error. %s" : "Konekta eraro al konservejo. %s", + "Storage is temporarily not available" : "Konservejo provizore ne disponeblas", + "Storage connection timeout. %s" : "Konekto al konservejo eltempiĝis. %s", + "Following databases are supported: %s" : "La jenaj datumbazoj estas subtenataj: %s", + "Following platforms are supported: %s" : "La jenaj platformoj estas subtenataj: %s", + "Overview" : "Superrigardo", + "Basic settings" : "Bazaj agordoj", + "Sharing" : "Kunhavigo", + "Security" : "Sekurigo", + "Groupware" : "Grupa kunlaborado", + "Personal info" : "Persona informo", + "Mobile & desktop" : "Porteblaj kaj labortablaj aplikaĵoj", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Igi la „apps“ dosierujon alirebla de la servilo aŭ malŝalti la aplikaĵejon en la agordodosiero kutime solvas tiun problemon. Vidu %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es.js b/docker/overlays/nextcloud/html/lib/l10n/es.js new file mode 100644 index 0000000..ae14ee4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "No se puede escribir en la carpeta \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él.", + "See %s" : "Ver %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él. Ver %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.", + "Sample configuration detected" : "Configuración de ejemplo detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que el ejemplo de configuración ha sido copiado. Esto podría afectar a su instalación, por lo que no tiene soporte. Lea la documentación antes de hacer cambios en config.php", + "Other activities" : "Otras actividades", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s, y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educación", + "Enterprise bundle" : "Pack para empresas", + "Groupware bundle" : "Pack groupware", + "Hub bundle" : "Pack para Hub", + "Social sharing bundle" : "Pack para compartir en redes", + "PHP %s or higher is required." : "Se requiere PHP %s o superior.", + "PHP with a version lower than %s is required." : "Se necesita una versión de PHP inferior a %s", + "%sbit or higher PHP required." : "Se requiere PHP %sbit o superior.", + "The following architectures are supported: %s" : "Las siguientes arquitecturas están soportadas: %s", + "The following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "The command line tool %s could not be found" : "No se encontró la herramienta %s de línea de comandos", + "The library %s is not available." : "La biblioteca %s no está disponible", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Se requiere la biblioteca %1$s con una versión mayor que %2$s. Está disponible la versión %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Se requiera la biblioteca %1$s con una versión menor que %2$s. Está disponible la versión %3$s.", + "The following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Server version %s or higher is required." : "Se necesita la versión %s o superior del servidor.", + "Server version %s or lower is required." : "Se necesita la versión %s o inferior del servidor. ", + "Logged in user must be an admin or sub admin" : "El usuario activo debe ser un administrador o subadministrador", + "Logged in user must be an admin" : "El usuario registrado debe ser un administrador", + "Wiping of device %s has started" : "El borrado del dispositivo %s ha empezado", + "Wiping of device »%s« has started" : "El borrado del dispositivo »%s« ha empezado", + "»%s« started remote wipe" : "»%s« ha empezado el borrado a distancia", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "El dispositivo o la aplicación »%s« ha empezado el proceso de borrado a distancia. Vas a recibir otro mensaje por correo una vez que el proceso haya concluido.", + "Wiping of device %s has finished" : "El borrado del dispositivo %s ha concluido", + "Wiping of device »%s« has finished" : "El borrado del dispositivo »%s« ha concluido", + "»%s« finished remote wipe" : "»%s« ha acabado el borrado a distancia", + "Device or application »%s« has finished the remote wipe process." : "El dispositivo o la aplicación »%s« ha concluido el proceso de borrado a distancia.", + "Remote wipe started" : "Borrado remoto comenzado.", + "A remote wipe was started on device %s" : "Se ha iniciado un borrado remoto en el dispositivo %s.", + "Remote wipe finished" : "Borrado remoto finalizado", + "The remote wipe on %s has finished" : "El borrado remoto en %s ha finalizado", + "Authentication" : "Autentificación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen no válida", + "Avatar image is not square" : "La imagen de avatar no es cuadrada", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["dentro de %n día","dentro de %n días"], + "_%n day ago_::_%n days ago_" : ["Hace %n día","hace %n días"], + "next month" : "mes siguiente", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["dentro de %n mes","dentro de %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","hace %n meses"], + "next year" : "año que viene", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["dentro de %n año","dentro de %n años"], + "_%n year ago_::_%n years ago_" : ["Hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["dentro de %n hora","dentro de %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["dentro de %n minuto","dentro de %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","hace %n minutos"], + "in a few seconds" : "en unos segundos", + "seconds ago" : "hace segundos", + "Empty file" : "Archivo vacío", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID %s no existe. Por favor, actívalo en la configuración de apps o contacta con tu administrador.", + "File name is a reserved word" : "El nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un carácter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "No se puede dejar el nombre en blanco.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "No se puede instalar la app \"%s\" debido a que no se puede leer la información de la app.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "No se puede instalar la aplicación \"%s\" porque no es compatible con esta versión del servidor.", + "__language_name__" : "Español (España)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no responda.", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuración", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Usuario desconocido", + "Additional settings" : "Configuración adicional", + "%s enter the database username and name." : "%s introduzca el nombre de usuario y la contraseña de la BBDD.", + "%s enter the database username." : "%s introduzca el usuario de la base de datos.", + "%s enter the database name." : "%s introduzca el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s puede utilizar puntos en el nombre de la base de datos", + "MySQL username and/or password not valid" : "Usuario y/o contraseña de MySQL no válidos", + "You need to enter details of an existing account." : "Tienes que introducir los datos de una cuenta existente.", + "Oracle connection could not be established" : "No se pudo establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle no válidos", + "PostgreSQL username and/or password not valid" : "Usuario y/o contraseña de PostgreSQL no válidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X no está soportado y %s no funcionará bien en esta plataforma. ¡Úsala bajo tu propio riesgo! ", + "For the best results, please consider using a GNU/Linux server instead." : "Para obtener los mejores resultados, considera utilizar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Parece que esta instancia %s está funcionando en un entorno PHP de 32-bits y el open_basedir se ha configurado en php.ini. Esto acarreará problemas con archivos de tamaño superior a 4GB y resulta totalmente desaconsejado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, quite el ajuste de open_basedir —dentro de su php.ini— o pásese a PHP de 64 bits.", + "Set an admin username." : "Configurar un nombre de usuario del administrador", + "Set an admin password." : "Configurar la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID de Nube federada no válida", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartido %s debe implementar la interfaz OCP\\Share_Backend", + "Sharing backend %s not found" : "El motor compartido %s no se ha encontrado", + "Sharing backend for %s not found" : "Motor compartido para %s no encontrado", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s ha compartido «%2$s» contigo y quiere añadir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s ha compartido «%2$s» contigo y quiere añadir", + "»%s« added a note to a file shared with you" : "«%s» ha añadido una nota a un archivo compartido contigo", + "Open »%s«" : "Abrir »%s« ", + "%1$s via %2$s" : "%1$s vía %2$s", + "You are not allowed to share %s" : "Usted no está autorizado para compartir %s", + "Can’t increase permissions of %s" : "No se pueden aumentar los permisos de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "Ha pasado la fecha de caducidad", + "Can’t set expiration date more than %s days in the future" : "No se puede establecer la fecha de expiración a más de %s días en el futuro", + "%1$s shared »%2$s« with you" : "%1$s ha compartido «%2$s» contigo", + "%1$s shared »%2$s« with you." : "%1$s ha compartido «%2$s» contigo.", + "Click the button below to open it." : "Haz clic en el botón de abajo para abrirlo.", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No puede encontrar la categoría \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mié.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sáb.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "enero", + "February" : "febrero", + "March" : "marzo", + "April" : "abril", + "May" : "mayo", + "June" : "junio", + "July" : "julio", + "August" : "agosto", + "September" : "septiembre", + "October" : "octubre", + "November" : "noviembre", + "December" : "diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo los siguientes caracteres están permitidos en un nombre de usuario: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Se debe proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El nombre de usuario contiene espacios en blanco al principio o al final", + "Username must not consist of dots only" : "El nombre de usuario no debe consistir solo de puntos", + "Username is invalid because files already exist for this user" : "El nombre de usuario es incorrecto debido a a que los archivos ya existen para este usuario", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "El nombre de usuario ya está en uso", + "Could not create user" : "No se ha podido crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Login cancelado por la app", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "No se ha podido instlaar la app «%1$s» porque no se cumplen las siguientes dependencias: %2$s", + "a safe home for all your data" : "un hogar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente ocupado, por favor inténtelo de nuevo más tarde", + "Can't read file" : "No se puede leer archivo", + "Application is not enabled" : "La aplicación no está habilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "Token caducado. Por favor, recarge la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No están instalados los drivers de BBDD (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory" : "No se puede escribir el el directorio de configuración", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración. Ver %s", + "Cannot write into \"apps\" directory" : "No se puede escribir en el directorio de \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Se podría solucionar esto dando al servidor web acceso de escritura al directorio de apps o desactivando la tienda de apps en el archivo de configuración.", + "Cannot create \"data\" directory" : "No se puede crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Habitualmente, esto puede arreglarse dando al servidor web acceso de escritura al directorio raíz. Véase %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Habitualmente, los permisos pueden arreglarse dando al servidor web acceso de escritura al directorio raíz. Véase %s", + "Setting locale to %s failed" : "Ha fallado la activación de la localización %s ", + "Please install one of these locales on your system and restart your webserver." : "Instale uno de estos idiomas en su sistema y reinicie su servidor web.", + "PHP module %s not installed." : "El módulo PHP %s no está instalado.", + "Please ask your server administrator to install the module." : "Consulte al administrador de su servidor para instalar el módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está dispuesta en \"%s\" en lugar del valor esperado \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para solucionarlo, defina la función mbstring.func_overload a 0 en su php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en línea. Esto hará que varias aplicaciones principales estén inaccesibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran", + "Please ask your server administrator to restart the web server." : "Consulte al administrador de su servidor para reiniciar el servidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL 9 o superior requerido.", + "Please upgrade your database version" : "Actualice su versión de base de datos.", + "Your data directory is readable by other users" : "Tu directorio de datos puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, cambia los permisos a 0770 para que el directorio no se pueda mostrar a otros usuarios.", + "Your data directory must be an absolute path" : "Su directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Compruebe el valor de \"datadirectory\" en su configuración.", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrate de que existe un archivo llamado \".ocdata\" en la raíz del directorio de datos.", + "Action \"%s\" not supported or implemented." : "La acción \"%s\" no está soportada o implementada.", + "Authentication failed, wrong token or provider ID given" : "La autentificación ha fallado. Se ha dado un token o una ID de proveedor erróneos.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Faltan parámetros para completar la petición. Parámetros que faltan: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "La ID «%1$s» ya está siendo usada por el proveedor de federación en la nube «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "El proveedor de nube federada con ID \"%s\" no existe.", + "Could not obtain lock type %d on \"%s\"." : "No se pudo realizar el bloqueo %d en \"%s\".", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración de almacenamiento incompleta. %s", + "Storage connection error. %s" : "Error de conexión de almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamiento no esta disponible temporalmente", + "Storage connection timeout. %s" : "Tiempo de conexión de almacenamiento agotado. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Vista general", + "Basic settings" : "Ajustes básicos", + "Sharing" : "Compartir", + "Security" : "Seguridad", + "Groupware" : "Groupware", + "Personal info" : "Información personal", + "Mobile & desktop" : "Móvil y escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Habitualmente, esto puede arreglarse dando al servidor web permisos de escritura al directorio de apps o desactivando la tienda de apps en el archivo de configuración. Véase %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es.json b/docker/overlays/nextcloud/html/lib/l10n/es.json new file mode 100644 index 0000000..fb6e53e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "No se puede escribir en la carpeta \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él.", + "See %s" : "Ver %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él. Ver %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.", + "Sample configuration detected" : "Configuración de ejemplo detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que el ejemplo de configuración ha sido copiado. Esto podría afectar a su instalación, por lo que no tiene soporte. Lea la documentación antes de hacer cambios en config.php", + "Other activities" : "Otras actividades", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s, y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educación", + "Enterprise bundle" : "Pack para empresas", + "Groupware bundle" : "Pack groupware", + "Hub bundle" : "Pack para Hub", + "Social sharing bundle" : "Pack para compartir en redes", + "PHP %s or higher is required." : "Se requiere PHP %s o superior.", + "PHP with a version lower than %s is required." : "Se necesita una versión de PHP inferior a %s", + "%sbit or higher PHP required." : "Se requiere PHP %sbit o superior.", + "The following architectures are supported: %s" : "Las siguientes arquitecturas están soportadas: %s", + "The following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "The command line tool %s could not be found" : "No se encontró la herramienta %s de línea de comandos", + "The library %s is not available." : "La biblioteca %s no está disponible", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Se requiere la biblioteca %1$s con una versión mayor que %2$s. Está disponible la versión %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Se requiera la biblioteca %1$s con una versión menor que %2$s. Está disponible la versión %3$s.", + "The following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Server version %s or higher is required." : "Se necesita la versión %s o superior del servidor.", + "Server version %s or lower is required." : "Se necesita la versión %s o inferior del servidor. ", + "Logged in user must be an admin or sub admin" : "El usuario activo debe ser un administrador o subadministrador", + "Logged in user must be an admin" : "El usuario registrado debe ser un administrador", + "Wiping of device %s has started" : "El borrado del dispositivo %s ha empezado", + "Wiping of device »%s« has started" : "El borrado del dispositivo »%s« ha empezado", + "»%s« started remote wipe" : "»%s« ha empezado el borrado a distancia", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "El dispositivo o la aplicación »%s« ha empezado el proceso de borrado a distancia. Vas a recibir otro mensaje por correo una vez que el proceso haya concluido.", + "Wiping of device %s has finished" : "El borrado del dispositivo %s ha concluido", + "Wiping of device »%s« has finished" : "El borrado del dispositivo »%s« ha concluido", + "»%s« finished remote wipe" : "»%s« ha acabado el borrado a distancia", + "Device or application »%s« has finished the remote wipe process." : "El dispositivo o la aplicación »%s« ha concluido el proceso de borrado a distancia.", + "Remote wipe started" : "Borrado remoto comenzado.", + "A remote wipe was started on device %s" : "Se ha iniciado un borrado remoto en el dispositivo %s.", + "Remote wipe finished" : "Borrado remoto finalizado", + "The remote wipe on %s has finished" : "El borrado remoto en %s ha finalizado", + "Authentication" : "Autentificación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen no válida", + "Avatar image is not square" : "La imagen de avatar no es cuadrada", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["dentro de %n día","dentro de %n días"], + "_%n day ago_::_%n days ago_" : ["Hace %n día","hace %n días"], + "next month" : "mes siguiente", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["dentro de %n mes","dentro de %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","hace %n meses"], + "next year" : "año que viene", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["dentro de %n año","dentro de %n años"], + "_%n year ago_::_%n years ago_" : ["Hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["dentro de %n hora","dentro de %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["dentro de %n minuto","dentro de %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","hace %n minutos"], + "in a few seconds" : "en unos segundos", + "seconds ago" : "hace segundos", + "Empty file" : "Archivo vacío", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID %s no existe. Por favor, actívalo en la configuración de apps o contacta con tu administrador.", + "File name is a reserved word" : "El nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un carácter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "No se puede dejar el nombre en blanco.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "No se puede instalar la app \"%s\" debido a que no se puede leer la información de la app.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "No se puede instalar la aplicación \"%s\" porque no es compatible con esta versión del servidor.", + "__language_name__" : "Español (España)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no responda.", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuración", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Usuario desconocido", + "Additional settings" : "Configuración adicional", + "%s enter the database username and name." : "%s introduzca el nombre de usuario y la contraseña de la BBDD.", + "%s enter the database username." : "%s introduzca el usuario de la base de datos.", + "%s enter the database name." : "%s introduzca el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s puede utilizar puntos en el nombre de la base de datos", + "MySQL username and/or password not valid" : "Usuario y/o contraseña de MySQL no válidos", + "You need to enter details of an existing account." : "Tienes que introducir los datos de una cuenta existente.", + "Oracle connection could not be established" : "No se pudo establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle no válidos", + "PostgreSQL username and/or password not valid" : "Usuario y/o contraseña de PostgreSQL no válidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X no está soportado y %s no funcionará bien en esta plataforma. ¡Úsala bajo tu propio riesgo! ", + "For the best results, please consider using a GNU/Linux server instead." : "Para obtener los mejores resultados, considera utilizar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Parece que esta instancia %s está funcionando en un entorno PHP de 32-bits y el open_basedir se ha configurado en php.ini. Esto acarreará problemas con archivos de tamaño superior a 4GB y resulta totalmente desaconsejado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, quite el ajuste de open_basedir —dentro de su php.ini— o pásese a PHP de 64 bits.", + "Set an admin username." : "Configurar un nombre de usuario del administrador", + "Set an admin password." : "Configurar la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID de Nube federada no válida", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartido %s debe implementar la interfaz OCP\\Share_Backend", + "Sharing backend %s not found" : "El motor compartido %s no se ha encontrado", + "Sharing backend for %s not found" : "Motor compartido para %s no encontrado", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s ha compartido «%2$s» contigo y quiere añadir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s ha compartido «%2$s» contigo y quiere añadir", + "»%s« added a note to a file shared with you" : "«%s» ha añadido una nota a un archivo compartido contigo", + "Open »%s«" : "Abrir »%s« ", + "%1$s via %2$s" : "%1$s vía %2$s", + "You are not allowed to share %s" : "Usted no está autorizado para compartir %s", + "Can’t increase permissions of %s" : "No se pueden aumentar los permisos de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "Ha pasado la fecha de caducidad", + "Can’t set expiration date more than %s days in the future" : "No se puede establecer la fecha de expiración a más de %s días en el futuro", + "%1$s shared »%2$s« with you" : "%1$s ha compartido «%2$s» contigo", + "%1$s shared »%2$s« with you." : "%1$s ha compartido «%2$s» contigo.", + "Click the button below to open it." : "Haz clic en el botón de abajo para abrirlo.", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No puede encontrar la categoría \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mié.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sáb.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "enero", + "February" : "febrero", + "March" : "marzo", + "April" : "abril", + "May" : "mayo", + "June" : "junio", + "July" : "julio", + "August" : "agosto", + "September" : "septiembre", + "October" : "octubre", + "November" : "noviembre", + "December" : "diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo los siguientes caracteres están permitidos en un nombre de usuario: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Se debe proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El nombre de usuario contiene espacios en blanco al principio o al final", + "Username must not consist of dots only" : "El nombre de usuario no debe consistir solo de puntos", + "Username is invalid because files already exist for this user" : "El nombre de usuario es incorrecto debido a a que los archivos ya existen para este usuario", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "El nombre de usuario ya está en uso", + "Could not create user" : "No se ha podido crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Login cancelado por la app", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "No se ha podido instlaar la app «%1$s» porque no se cumplen las siguientes dependencias: %2$s", + "a safe home for all your data" : "un hogar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente ocupado, por favor inténtelo de nuevo más tarde", + "Can't read file" : "No se puede leer archivo", + "Application is not enabled" : "La aplicación no está habilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "Token caducado. Por favor, recarge la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No están instalados los drivers de BBDD (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory" : "No se puede escribir el el directorio de configuración", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Se podría solucionar esto dándole al servidor permisos de escritura del directorio de configuración. Ver %s", + "Cannot write into \"apps\" directory" : "No se puede escribir en el directorio de \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Se podría solucionar esto dando al servidor web acceso de escritura al directorio de apps o desactivando la tienda de apps en el archivo de configuración.", + "Cannot create \"data\" directory" : "No se puede crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Habitualmente, esto puede arreglarse dando al servidor web acceso de escritura al directorio raíz. Véase %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Habitualmente, los permisos pueden arreglarse dando al servidor web acceso de escritura al directorio raíz. Véase %s", + "Setting locale to %s failed" : "Ha fallado la activación de la localización %s ", + "Please install one of these locales on your system and restart your webserver." : "Instale uno de estos idiomas en su sistema y reinicie su servidor web.", + "PHP module %s not installed." : "El módulo PHP %s no está instalado.", + "Please ask your server administrator to install the module." : "Consulte al administrador de su servidor para instalar el módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está dispuesta en \"%s\" en lugar del valor esperado \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para solucionarlo, defina la función mbstring.func_overload a 0 en su php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en línea. Esto hará que varias aplicaciones principales estén inaccesibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran", + "Please ask your server administrator to restart the web server." : "Consulte al administrador de su servidor para reiniciar el servidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL 9 o superior requerido.", + "Please upgrade your database version" : "Actualice su versión de base de datos.", + "Your data directory is readable by other users" : "Tu directorio de datos puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, cambia los permisos a 0770 para que el directorio no se pueda mostrar a otros usuarios.", + "Your data directory must be an absolute path" : "Su directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Compruebe el valor de \"datadirectory\" en su configuración.", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrate de que existe un archivo llamado \".ocdata\" en la raíz del directorio de datos.", + "Action \"%s\" not supported or implemented." : "La acción \"%s\" no está soportada o implementada.", + "Authentication failed, wrong token or provider ID given" : "La autentificación ha fallado. Se ha dado un token o una ID de proveedor erróneos.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Faltan parámetros para completar la petición. Parámetros que faltan: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "La ID «%1$s» ya está siendo usada por el proveedor de federación en la nube «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "El proveedor de nube federada con ID \"%s\" no existe.", + "Could not obtain lock type %d on \"%s\"." : "No se pudo realizar el bloqueo %d en \"%s\".", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración de almacenamiento incompleta. %s", + "Storage connection error. %s" : "Error de conexión de almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamiento no esta disponible temporalmente", + "Storage connection timeout. %s" : "Tiempo de conexión de almacenamiento agotado. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Vista general", + "Basic settings" : "Ajustes básicos", + "Sharing" : "Compartir", + "Security" : "Seguridad", + "Groupware" : "Groupware", + "Personal info" : "Información personal", + "Mobile & desktop" : "Móvil y escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Habitualmente, esto puede arreglarse dando al servidor web permisos de escritura al directorio de apps o desactivando la tienda de apps en el archivo de configuración. Véase %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_419.js b/docker/overlays/nextcloud/html/lib/l10n/es_419.js new file mode 100644 index 0000000..67a31c2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_419.js @@ -0,0 +1,197 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Latin America)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Resumen", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_419.json b/docker/overlays/nextcloud/html/lib/l10n/es_419.json new file mode 100644 index 0000000..86c6375 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_419.json @@ -0,0 +1,195 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Latin America)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Resumen", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_AR.js b/docker/overlays/nextcloud/html/lib/l10n/es_AR.js new file mode 100644 index 0000000..a2108fa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_AR.js @@ -0,0 +1,179 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se soluciona dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede descomponer su instalacón y no está soportado. Favor de leer la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHPH %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "yesterday" : "ayer", + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "last month" : "mes pasado", + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "last year" : "año pasado", + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Favor de habilitarlo en sus configuraciones de aplicación o contacte a su administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Argentina)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, favor de no contestarlo. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Ajustes", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingrese el nombre del usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresar el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puede utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesita ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "El nombre de usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El nombre de usuario y/o contraseña de PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Uselo bajo su propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, favor de cosiderar usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Favor de eliminar el ajuste open_basedir de su archivo php.ini o cambie a PHP de 64 bits. ", + "Set an admin username." : "Configurar un nombre de usuario del administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID de Nube Federada Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tiene permitido compartir %s", + "Expiration date is in the past" : "La fecha de expiración ya ha pasado", + "Click the button below to open it." : "Haga click en el botón de abajo para abrirlo.", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el nombre de usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Se debe proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El nombre del usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El nombre de usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese nombre de usuario ya está en uso", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos sus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, favor de intentarlo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Favor de recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuenta con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Favor de ver %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Favor de ver %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Favor de ver %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Favor de instalar uno de las siguientes configuraciones locales en su sistema y reinicie su servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Favor de solicitar a su adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establezca mbstring.func_overload a 0 en su archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s esta instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, favor de actualizar la versión de su libxml2 y reinicie su servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Favor de solicitar al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Favor de actualizar la versión de la base de datos", + "Your data directory is readable by other users" : "Su direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Favor de cambiar los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Su direcctorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifique el valor de \"datadirectory\" en su configuración", + "Your data directory is invalid" : "Su directorio de datos es inválido", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "Se agotó el tiempo de conexión del almacenamiento. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información Personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Favor de ver %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_AR.json b/docker/overlays/nextcloud/html/lib/l10n/es_AR.json new file mode 100644 index 0000000..623ba07 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_AR.json @@ -0,0 +1,177 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se soluciona dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede descomponer su instalacón y no está soportado. Favor de leer la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHPH %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "yesterday" : "ayer", + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "last month" : "mes pasado", + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "last year" : "año pasado", + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Favor de habilitarlo en sus configuraciones de aplicación o contacte a su administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Argentina)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, favor de no contestarlo. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Ajustes", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingrese el nombre del usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresar el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puede utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesita ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "El nombre de usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El nombre de usuario y/o contraseña de PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Uselo bajo su propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, favor de cosiderar usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Favor de eliminar el ajuste open_basedir de su archivo php.ini o cambie a PHP de 64 bits. ", + "Set an admin username." : "Configurar un nombre de usuario del administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID de Nube Federada Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tiene permitido compartir %s", + "Expiration date is in the past" : "La fecha de expiración ya ha pasado", + "Click the button below to open it." : "Haga click en el botón de abajo para abrirlo.", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el nombre de usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Se debe proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El nombre del usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El nombre de usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese nombre de usuario ya está en uso", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos sus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, favor de intentarlo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Favor de recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuenta con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Favor de ver %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Favor de ver %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Favor de ver %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Favor de instalar uno de las siguientes configuraciones locales en su sistema y reinicie su servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Favor de solicitar a su adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establezca mbstring.func_overload a 0 en su archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s esta instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, favor de actualizar la versión de su libxml2 y reinicie su servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Favor de solicitar al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Favor de actualizar la versión de la base de datos", + "Your data directory is readable by other users" : "Su direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Favor de cambiar los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Su direcctorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifique el valor de \"datadirectory\" en su configuración", + "Your data directory is invalid" : "Su directorio de datos es inválido", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "Se agotó el tiempo de conexión del almacenamiento. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información Personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Favor de ver %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CL.js b/docker/overlays/nextcloud/html/lib/l10n/es_CL.js new file mode 100644 index 0000000..902d615 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CL.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Chile)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CL.json b/docker/overlays/nextcloud/html/lib/l10n/es_CL.json new file mode 100644 index 0000000..cb22cc0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CL.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Chile)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CO.js b/docker/overlays/nextcloud/html/lib/l10n/es_CO.js new file mode 100644 index 0000000..3671124 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CO.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Colombia)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CO.json b/docker/overlays/nextcloud/html/lib/l10n/es_CO.json new file mode 100644 index 0000000..d4620eb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CO.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Colombia)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CR.js b/docker/overlays/nextcloud/html/lib/l10n/es_CR.js new file mode 100644 index 0000000..ae87bca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CR.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Costa Rica)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_CR.json b/docker/overlays/nextcloud/html/lib/l10n/es_CR.json new file mode 100644 index 0000000..e434fc0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_CR.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Costa Rica)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_DO.js b/docker/overlays/nextcloud/html/lib/l10n/es_DO.js new file mode 100644 index 0000000..a7b263c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_DO.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Dominican Republic)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_DO.json b/docker/overlays/nextcloud/html/lib/l10n/es_DO.json new file mode 100644 index 0000000..8b203c9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_DO.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Dominican Republic)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_EC.js b/docker/overlays/nextcloud/html/lib/l10n/es_EC.js new file mode 100644 index 0000000..96f3e87 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_EC.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Ecuador)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_EC.json b/docker/overlays/nextcloud/html/lib/l10n/es_EC.json new file mode 100644 index 0000000..5e38322 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_EC.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Ecuador)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_GT.js b/docker/overlays/nextcloud/html/lib/l10n/es_GT.js new file mode 100644 index 0000000..5d57a81 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_GT.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Guatemala)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_GT.json b/docker/overlays/nextcloud/html/lib/l10n/es_GT.json new file mode 100644 index 0000000..97c9580 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_GT.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Guatemala)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_HN.js b/docker/overlays/nextcloud/html/lib/l10n/es_HN.js new file mode 100644 index 0000000..7350f25 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_HN.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Honduras)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_HN.json b/docker/overlays/nextcloud/html/lib/l10n/es_HN.json new file mode 100644 index 0000000..157a641 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_HN.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Honduras)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_MX.js b/docker/overlays/nextcloud/html/lib/l10n/es_MX.js new file mode 100644 index 0000000..7fbf224 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_MX.js @@ -0,0 +1,199 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (México)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "Mobile & desktop" : "Móvil & escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_MX.json b/docker/overlays/nextcloud/html/lib/l10n/es_MX.json new file mode 100644 index 0000000..79bfcf3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_MX.json @@ -0,0 +1,197 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (México)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "Mobile & desktop" : "Móvil & escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_NI.js b/docker/overlays/nextcloud/html/lib/l10n/es_NI.js new file mode 100644 index 0000000..2f3157a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_NI.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Nicaragua)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_NI.json b/docker/overlays/nextcloud/html/lib/l10n/es_NI.json new file mode 100644 index 0000000..08cff44 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_NI.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Nicaragua)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PA.js b/docker/overlays/nextcloud/html/lib/l10n/es_PA.js new file mode 100644 index 0000000..29fae00 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PA.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Panama)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PA.json b/docker/overlays/nextcloud/html/lib/l10n/es_PA.json new file mode 100644 index 0000000..2733b4c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PA.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Panama)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PE.js b/docker/overlays/nextcloud/html/lib/l10n/es_PE.js new file mode 100644 index 0000000..ef211dc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PE.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Peru)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PE.json b/docker/overlays/nextcloud/html/lib/l10n/es_PE.json new file mode 100644 index 0000000..9df37bd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PE.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Peru)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PR.js b/docker/overlays/nextcloud/html/lib/l10n/es_PR.js new file mode 100644 index 0000000..7441818 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PR.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Puerto Rico)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PR.json b/docker/overlays/nextcloud/html/lib/l10n/es_PR.json new file mode 100644 index 0000000..2c4a054 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PR.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Puerto Rico)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PY.js b/docker/overlays/nextcloud/html/lib/l10n/es_PY.js new file mode 100644 index 0000000..6a79271 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PY.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Paraguay)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_PY.json b/docker/overlays/nextcloud/html/lib/l10n/es_PY.json new file mode 100644 index 0000000..fa93065 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_PY.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Paraguay)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_SV.js b/docker/overlays/nextcloud/html/lib/l10n/es_SV.js new file mode 100644 index 0000000..8260420 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_SV.js @@ -0,0 +1,198 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (El Salvador)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_SV.json b/docker/overlays/nextcloud/html/lib/l10n/es_SV.json new file mode 100644 index 0000000..d8b8d4c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_SV.json @@ -0,0 +1,196 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Logged in user must be an admin" : "El usuario firmado debe ser un administrador", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (El Salvador)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Cerrar sesión", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Overview" : "Generalidades", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_UY.js b/docker/overlays/nextcloud/html/lib/l10n/es_UY.js new file mode 100644 index 0000000..d85c7e1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_UY.js @@ -0,0 +1,196 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Uruguay)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/es_UY.json b/docker/overlays/nextcloud/html/lib/l10n/es_UY.json new file mode 100644 index 0000000..fc66bc4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/es_UY.json @@ -0,0 +1,194 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "¡No se puede escribir en el directorio \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Esto generalmente se resuelve dándole al servidor web acceso para escribir en el directorio config. ", + "See %s" : "Ver %s", + "Sample configuration detected" : "Se ha detectado la configuración de muestra", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php", + "%1$s and %2$s" : "%1$s y %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s y %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s y %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s y %5$s", + "Education Edition" : "Edición Educativa", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de Groupware", + "Social sharing bundle" : "Paquete para compartir en redes sociales", + "PHP %s or higher is required." : "Se requiere de PHP %s o superior.", + "PHP with a version lower than %s is required." : "PHP con una versión inferiror a la %s es requerido. ", + "%sbit or higher PHP required." : "se requiere PHP para %sbit o superior.", + "The command line tool %s could not be found" : "No fue posible encontar la herramienta de línea de comando %s", + "The library %s is not available." : "La biblioteca %s no está disponible. ", + "Server version %s or higher is required." : "Se requiere la versión del servidor %s o superior. ", + "Server version %s or lower is required." : "La versión del servidor %s o inferior es requerdia. ", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de archivo desconocido", + "Invalid image" : "Imagen inválida", + "Avatar image is not square" : "La imagen del avatar no es un cuadrado", + "today" : "hoy", + "tomorrow" : "mañana", + "yesterday" : "ayer", + "_in %n day_::_in %n days_" : ["en %n día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hace %n día","hace %n días"], + "next month" : "próximo mes", + "last month" : "mes pasado", + "_in %n month_::_in %n months_" : ["en %n mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["Hace %n mes","Hace %n meses"], + "next year" : "próximo año", + "last year" : "año pasado", + "_in %n year_::_in %n years_" : ["en %n año","en %n años"], + "_%n year ago_::_%n years ago_" : ["hace %n año","hace %n años"], + "_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["Hace %n hora","Hace %n horas"], + "_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["Hace %n minuto","Hace %n minutos"], + "in a few seconds" : "en algunos segundos", + "seconds ago" : "hace segundos", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "El módulo con ID: %sno existe. Por favor hablíitalo en tus configuraciones de aplicación o contacta a tu administrador. ", + "File name is a reserved word" : "Nombre de archivo es una palabra reservada", + "File name contains at least one invalid character" : "El nombre del archivo contiene al menos un caracter inválido", + "File name is too long" : "El nombre del archivo es demasiado largo", + "Dot files are not allowed" : "Los archivos Dot no están permitidos", + "Empty filename is not allowed" : "El uso de nombres de archivo vacíos no está permitido", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "La aplicación \"%s\" no puede ser instalada porque el archivo appinfo no se puede leer. ", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión del servidor. ", + "__language_name__" : "Español (Uruguay)", + "This is an automatically sent email, please do not reply." : "Este es un correo enviado automáticamente, por favor no lo contestes. ", + "Help" : "Ayuda", + "Apps" : "Aplicaciones", + "Settings" : "Configuraciones", + "Log out" : "Salir", + "Users" : "Usuarios", + "Unknown user" : "Ususario desconocido", + "Additional settings" : "Configuraciones adicionales", + "%s enter the database username and name." : "%s ingresa el usuario y nombre de la base de datos", + "%s enter the database username." : "%s ingresa el nombre de usuario de la base de datos.", + "%s enter the database name." : "%s ingresar el nombre de la base de datos", + "%s you may not use dots in the database name" : "%s no puedes utilizar puntos en el nombre de la base de datos", + "You need to enter details of an existing account." : "Necesitas ingresar los detalles de una cuenta existente.", + "Oracle connection could not be established" : "No fue posible establecer la conexión a Oracle", + "Oracle username and/or password not valid" : "Usuario y/o contraseña de Oracle inválidos", + "PostgreSQL username and/or password not valid" : "El Usuario y/o Contraseña de PostgreSQL inválido(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "OS X de Mac no está soportado y %s no funcionará correctamente en esta plataforma ¡Úsalo bajo tu propio riesgo!", + "For the best results, please consider using a GNU/Linux server instead." : "Para mejores resultados, por favor cosidera usar en su lugar un servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Al parecer esta instancia %s está corriendo en un ambiente PHP de 32-bits y el open_basedir ha sido configurado en el archivo php.ini. Esto generará problemas con archivos de más de 4GB de tamaño y es altamente desalentado. ", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor elimina el ajuste open_basedir de tu archivo php.ini o cambia a PHP de 64 bits. ", + "Set an admin username." : "Establecer un Usuario administrador", + "Set an admin password." : "Establecer la contraseña del administrador.", + "Can't create or write into the data directory %s" : "No es posible crear o escribir en el directorio de datos %s", + "Invalid Federated Cloud ID" : "ID Inválido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El backend %s que comparte debe implementar la interface OCP\\Share_Backend", + "Sharing backend %s not found" : "No fue encontrado el Backend que comparte %s ", + "Sharing backend for %s not found" : "No fue encontrado el Backend que comparte para %s", + "Open »%s«" : "Abrir »%s«", + "You are not allowed to share %s" : "No tienes permitido compartir %s", + "Can’t increase permissions of %s" : "No es posible incrementar los privilegios de %s", + "Files can’t be shared with delete permissions" : "Los archivos no se pueden compartir con permisos de borrado", + "Files can’t be shared with create permissions" : "Los archivos no se pueden compartir con permisos de creación", + "Expiration date is in the past" : "La fecha de expiración se encuentra en el pasado", + "Can’t set expiration date more than %s days in the future" : "No es posible establecer la fecha de expiración más allá de %s días en el futuro", + "Click the button below to open it." : "Haz click en el botón inferior para abrirlo. ", + "The requested share does not exist anymore" : "El recurso compartido solicitado ya no existe", + "Could not find category \"%s\"" : "No fue posible encontrar la categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Lunes", + "Tuesday" : "Martes", + "Wednesday" : "Miércoles", + "Thursday" : "Jueves", + "Friday" : "Viernes", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Jue.", + "Fri." : "Vie.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Ju", + "Fr" : "Vi", + "Sa" : "Sa", + "January" : "Enero", + "February" : "Febrero", + "March" : "Marzo", + "April" : "Abril", + "May" : "Mayo", + "June" : "Junio", + "July" : "Julio", + "August" : "Agosto", + "September" : "Septiembre", + "October" : "Octubre", + "November" : "Noviembre", + "December" : "Diciembre", + "Jan." : "Ene.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "May.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"", + "A valid username must be provided" : "Debes proporcionar un nombre de usuario válido", + "Username contains whitespace at the beginning or at the end" : "El usuario contiene un espacio en blanco al inicio o al final", + "Username must not consist of dots only" : "El usuario no debe consistir de solo puntos. ", + "A valid password must be provided" : "Se debe proporcionar una contraseña válida", + "The username is already being used" : "Ese usuario ya está en uso", + "Could not create user" : "No fue posible crear el usuario", + "User disabled" : "Usuario deshabilitado", + "Login canceled by app" : "Inicio de sesión cancelado por la aplicación", + "a safe home for all your data" : "un lugar seguro para todos tus datos", + "File is currently busy, please try again later" : "El archivo se encuentra actualmente en uso, por favor intentalo más tarde. ", + "Can't read file" : "No se puede leer el archivo", + "Application is not enabled" : "La aplicación está deshabilitada", + "Authentication error" : "Error de autenticación", + "Token expired. Please reload page." : "La ficha ha expirado. Por favor recarga la página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "No cuentas con controladores de base de datos (sqlite, mysql o postgresql) instalados. ", + "Cannot write into \"config\" directory" : "No fue posible escribir en el directorio \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio config. Por favor ve %s", + "Cannot write into \"apps\" directory" : "No fue posible escribir en el directorio \"apps\"", + "Cannot create \"data\" directory" : "No fue posible crear el directorio \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Por lo general los permisos se pueden corregir al darle al servidor web acceso de escritura al directorio raíz. Por favor ve %s.", + "Setting locale to %s failed" : "Se presentó una falla al establecer la regionalización a %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor instala uno de las siguientes configuraciones locales en tu sistema y reinicia tu servidor web", + "PHP module %s not installed." : "El módulo de PHP %s no está instalado. ", + "Please ask your server administrator to install the module." : "Por favor solicita a tu adminsitrador la instalación del módulo. ", + "PHP setting \"%s\" is not set to \"%s\"." : "El ajuste PHP \"%s\" no esta establecido a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "El cambiar este ajuste del archivo php.ini hará que Nextcloud corra de nuevo.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está establecido como \"%s\" en lugar del valor esperado de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corregir este tema, establece mbstring.func_overload a 0 en tu archivo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ", + "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Al parecer PHP está configurado para quitar los bloques de comentarios internos. Esto hará que varias aplicaciones principales sean inaccesibles. ", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Esto ha sido causado probablemente por un acelerador de caché como Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "¿Los módulos de PHP han sido instalados, pero se siguen enlistando como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor solicita al administrador reiniciar el servidor web. ", + "PostgreSQL >= 9 required" : "Se requiere PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualiza tu versión de la base de datos", + "Your data directory is readable by other users" : "Tu direcctorio data puede ser leído por otros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor cambia los permisos a 0770 para que el directorio no pueda ser enlistado por otros usuarios. ", + "Your data directory must be an absolute path" : "Tu directorio data debe ser una ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Verifica el valor de \"datadirectory\" en tu configuración", + "Your data directory is invalid" : "Tu directorio de datos es inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegurate de que exista una archivo llamado \".ocdata\" en la raíz del directorio de datos. ", + "Could not obtain lock type %d on \"%s\"." : "No fue posible obtener el tipo de bloqueo %d en \"%s\". ", + "Storage unauthorized. %s" : "Almacenamiento no autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta del almacenamiento. %s", + "Storage connection error. %s" : "Se presentó un error con la conexión al almacenamiento. %s", + "Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible", + "Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s", + "Following databases are supported: %s" : "Las siguientes bases de datos están soportadas: %s", + "Following platforms are supported: %s" : "Las siguientes plataformas están soportadas: %s", + "Basic settings" : "Configuraciones básicas", + "Sharing" : "Compartiendo", + "Security" : "Seguridad", + "Personal info" : "Información personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Por lo general esto se puede resolver al darle al servidor web acceso de escritura al directorio de las aplicaciones o deshabilitando la appstore en el archivo config. Por favor ve %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/et_EE.js b/docker/overlays/nextcloud/html/lib/l10n/et_EE.js new file mode 100644 index 0000000..e9ee700 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/et_EE.js @@ -0,0 +1,161 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Ei saa kirjutada \"config\" kataloogi!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tavaliselt saab selle lahendada andes veebiserverile seatete kataloogile \"config\" kirjutusõigused", + "See %s" : "Vaata %s", + "Sample configuration detected" : "Tuvastati näidisseaded", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Tuvastati, et kopeeriti näidisseaded. See võib lõhkuda sinu saidi ja see pole toetatud. Palun loe enne faili config.php muutmist dokumentatsiooni", + "%1$s and %2$s" : "%1$s ja %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ja %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ja %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ja %5$s", + "PHP %s or higher is required." : "PHP %s või uuem on nõutav.", + "PHP with a version lower than %s is required." : "Nõutud on PHP madalama versiooniga kui %s.", + "The command line tool %s could not be found" : "Käsurea töövahendit %s ei leitud", + "The library %s is not available." : "Teek %s pole saadaval.", + "Server version %s or higher is required." : "Serveri versioon %s või kõrgem on nõutav.", + "Server version %s or lower is required." : "Serveri versioon %s või madalam on nõutav.", + "Authentication" : "Autentimine", + "Unknown filetype" : "Tundmatu failitüüp", + "Invalid image" : "Vigane pilt", + "Avatar image is not square" : "Avatari pilt pole ruut", + "today" : "täna", + "tomorrow" : "homme", + "yesterday" : "eile", + "_%n day ago_::_%n days ago_" : ["%n päev tagasi","%n päeva tagasi"], + "next month" : "järgmine kuu", + "last month" : "viimasel kuul", + "next year" : "järgmine aasta", + "last year" : "viimasel aastal", + "_%n year ago_::_%n years ago_" : ["%n aasta tagasi","%n aastat tagasi"], + "_%n hour ago_::_%n hours ago_" : ["%n tund tagasi","%n tundi tagasi"], + "in a few seconds" : "mõne sekundi jooksul", + "seconds ago" : "sekundit tagasi", + "File name is a reserved word" : "Failinimi sisaldab keelatud sõna", + "File name contains at least one invalid character" : "Faili nimesonvähemalt üks keelatud märk", + "File name is too long" : "Faili nimi on liiga pikk", + "Dot files are not allowed" : "Punktiga failid pole lubatud", + "Empty filename is not allowed" : "Tühi failinimi pole lubatud", + "__language_name__" : "Eesti", + "This is an automatically sent email, please do not reply." : "See on automaatselt saadetud e-kiri, palun ära vasta.", + "Help" : "Abi", + "Apps" : "Rakendused", + "Settings" : "Seaded", + "Log out" : "Logi välja", + "Users" : "Kasutajad", + "Unknown user" : "Tundmatu kasutaja", + "Additional settings" : "Lisaseaded", + "%s enter the database username and name." : "%s sisesta andmebaasi kasutajatunnus ja nimi.", + "%s enter the database username." : "%s sisesta andmebaasi kasutajatunnus.", + "%s enter the database name." : "%s sisesta andmebaasi nimi.", + "%s you may not use dots in the database name" : "%s punktide kasutamine andmebaasi nimes pole lubatud", + "You need to enter details of an existing account." : "Sa pead sisestama olemasoleva konto andmed.", + "Oracle connection could not be established" : "Ei suuda luua ühendust Oracle baasiga", + "Oracle username and/or password not valid" : "Oracle kasutajatunnus ja/või parool pole õiged", + "PostgreSQL username and/or password not valid" : "PostgreSQL kasutajatunnus ja/või parool pole õiged", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X ei ole toetatud ja %s ei pruugi korralikult toimida sellel platvormil. Kasuta seda omal vastutusel!", + "For the best results, please consider using a GNU/Linux server instead." : "Parema tulemuse saavitamiseks palun kaalu serveris GNU/Linux kasutamist.", + "Set an admin username." : "Määra admin kasutajanimi.", + "Set an admin password." : "Määra admini parool.", + "Can't create or write into the data directory %s" : "Ei suuda luua või kirjutada andmete kataloogi %s", + "Invalid Federated Cloud ID" : "Vigane liitpilve ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Jagamise tagarakend %s peab kasutusele võtma OCP\\Share_Backend liidese", + "Sharing backend %s not found" : "Jagamise tagarakendit %s ei leitud", + "Sharing backend for %s not found" : "Jagamise tagarakendit %s jaoks ei leitud", + "Open »%s«" : "Ava »%s«", + "You are not allowed to share %s" : "Sul pole lubatud %s jagada", + "Can’t increase permissions of %s" : "Ei saa %s õigusi suurendada", + "Files can’t be shared with delete permissions" : "Faile ei saa jagada kustutamise õigusega", + "Files can’t be shared with create permissions" : "Faile ei saa jagada loomise õigusega", + "Expiration date is in the past" : "Aegumise kuupäev on minevikus", + "Can’t set expiration date more than %s days in the future" : "Ei sa määrata aegumise kuupäeva rohkem kui %s päeva tulevikus", + "Click the button below to open it." : "Vajuta allolevat nuppu, et see avada.", + "The requested share does not exist anymore" : "Soovitud jagamist enam ei eksisteeri", + "Could not find category \"%s\"" : "Ei leia kategooriat \"%s\"", + "Sunday" : "Pühapäev", + "Monday" : "Esmaspäev", + "Tuesday" : "Teisipäev", + "Wednesday" : "Kolmapäev", + "Thursday" : "Neljapäev", + "Friday" : "Reede", + "Saturday" : "Laupäev", + "Sun." : "P", + "Mon." : "E", + "Tue." : "T", + "Wed." : "K", + "Thu." : "N", + "Fri." : "R", + "Sat." : "L", + "Su" : "P", + "Mo" : "E", + "Tu" : "T", + "We" : "K", + "Th" : "N", + "Fr" : "R", + "Sa" : "L", + "January" : "Jaanuar", + "February" : "Veebruar", + "March" : "Märts", + "April" : "Aprill", + "May" : "Mai", + "June" : "Juuni", + "July" : "Juuli", + "August" : "August", + "September" : "September", + "October" : "Oktoober", + "November" : "November", + "December" : "Detsember", + "Jan." : "Jaan.", + "Feb." : "Veebr.", + "Mar." : "Märts.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Juuni.", + "Jul." : "Juuli.", + "Aug." : "Aug.", + "Sep." : "Sept.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dets.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kasutajanimes on lubatud ainult järgmised sümbolid: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Sisesta nõuetele vastav kasutajatunnus", + "Username contains whitespace at the beginning or at the end" : "Kasutajanime alguses või lõpus on tühik", + "Username must not consist of dots only" : "Kasutajanimi ei tohi koosneda ainult punktidest", + "A valid password must be provided" : "Sisesta nõuetele vastav parool", + "The username is already being used" : "Kasutajanimi on juba kasutuses", + "Could not create user" : "Ei saanud kasutajat luua", + "User disabled" : "Kasutaja deaktiveeritud", + "a safe home for all your data" : "turvaline koht sinu andmetele", + "File is currently busy, please try again later" : "Fail on hetkel kasutuses, proovi hiljem uuesti", + "Can't read file" : "Faili lugemine ebaõnnestus", + "Application is not enabled" : "Rakendus pole sisse lülitatud", + "Authentication error" : "Autentimise viga", + "Token expired. Please reload page." : "Kontrollkood aegus. Paelun lae leht uuesti.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ühtegi andmebaasi (sqlite, mysql või postgresql) draiverit pole paigaldatud.", + "Cannot write into \"config\" directory" : "Ei saa kirjutada \"config\" kataloogi", + "Cannot write into \"apps\" directory" : "Ei saa kirjutada \"apps\" kataloogi!", + "Cannot create \"data\" directory" : "Ei suuda luua \"data\" kataloogi", + "Setting locale to %s failed" : "Lokaadi %s määramine ebaõnnestus.", + "Please install one of these locales on your system and restart your webserver." : "Palun paigalda mõni neist lokaatides oma süsteemi ning taaskäivita veebiserver.", + "PHP module %s not installed." : "PHP moodulit %s pole paigaldatud.", + "Please ask your server administrator to install the module." : "Palu oma serveri haldajal moodul paigadalda.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "See on tõenäoliselt põhjustatud puhver/kiirendist nagu Zend OPcache või eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moodulid on paigaldatud, kuid neid näitatakse endiselt kui puuduolevad?", + "Please ask your server administrator to restart the web server." : "Palu oma serveri haldajal veebiserver taaskäivitada.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 on nõutav", + "Please upgrade your database version" : "Palun uuenda oma andmebaasi versiooni", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Palun muuda kataloogi õigused 0770-ks, et kataloogi sisu poleks teistele kasutajatele nähtav", + "Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust \"%s\".", + "Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav", + "Following databases are supported: %s" : "Toetatud on järgnevad andmebaasid: %s", + "Following platforms are supported: %s" : "Toetatud on järgnevad platformid: %s", + "Overview" : "Ülevaade", + "Basic settings" : "Põhiseaded", + "Sharing" : "Jagamine", + "Security" : "Turvalisus", + "Groupware" : "Grupitöö", + "Personal info" : "Isiklik info", + "Mobile & desktop" : "Mobiil ja töölaud" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/et_EE.json b/docker/overlays/nextcloud/html/lib/l10n/et_EE.json new file mode 100644 index 0000000..a0780f3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/et_EE.json @@ -0,0 +1,159 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Ei saa kirjutada \"config\" kataloogi!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tavaliselt saab selle lahendada andes veebiserverile seatete kataloogile \"config\" kirjutusõigused", + "See %s" : "Vaata %s", + "Sample configuration detected" : "Tuvastati näidisseaded", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Tuvastati, et kopeeriti näidisseaded. See võib lõhkuda sinu saidi ja see pole toetatud. Palun loe enne faili config.php muutmist dokumentatsiooni", + "%1$s and %2$s" : "%1$s ja %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ja %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ja %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ja %5$s", + "PHP %s or higher is required." : "PHP %s või uuem on nõutav.", + "PHP with a version lower than %s is required." : "Nõutud on PHP madalama versiooniga kui %s.", + "The command line tool %s could not be found" : "Käsurea töövahendit %s ei leitud", + "The library %s is not available." : "Teek %s pole saadaval.", + "Server version %s or higher is required." : "Serveri versioon %s või kõrgem on nõutav.", + "Server version %s or lower is required." : "Serveri versioon %s või madalam on nõutav.", + "Authentication" : "Autentimine", + "Unknown filetype" : "Tundmatu failitüüp", + "Invalid image" : "Vigane pilt", + "Avatar image is not square" : "Avatari pilt pole ruut", + "today" : "täna", + "tomorrow" : "homme", + "yesterday" : "eile", + "_%n day ago_::_%n days ago_" : ["%n päev tagasi","%n päeva tagasi"], + "next month" : "järgmine kuu", + "last month" : "viimasel kuul", + "next year" : "järgmine aasta", + "last year" : "viimasel aastal", + "_%n year ago_::_%n years ago_" : ["%n aasta tagasi","%n aastat tagasi"], + "_%n hour ago_::_%n hours ago_" : ["%n tund tagasi","%n tundi tagasi"], + "in a few seconds" : "mõne sekundi jooksul", + "seconds ago" : "sekundit tagasi", + "File name is a reserved word" : "Failinimi sisaldab keelatud sõna", + "File name contains at least one invalid character" : "Faili nimesonvähemalt üks keelatud märk", + "File name is too long" : "Faili nimi on liiga pikk", + "Dot files are not allowed" : "Punktiga failid pole lubatud", + "Empty filename is not allowed" : "Tühi failinimi pole lubatud", + "__language_name__" : "Eesti", + "This is an automatically sent email, please do not reply." : "See on automaatselt saadetud e-kiri, palun ära vasta.", + "Help" : "Abi", + "Apps" : "Rakendused", + "Settings" : "Seaded", + "Log out" : "Logi välja", + "Users" : "Kasutajad", + "Unknown user" : "Tundmatu kasutaja", + "Additional settings" : "Lisaseaded", + "%s enter the database username and name." : "%s sisesta andmebaasi kasutajatunnus ja nimi.", + "%s enter the database username." : "%s sisesta andmebaasi kasutajatunnus.", + "%s enter the database name." : "%s sisesta andmebaasi nimi.", + "%s you may not use dots in the database name" : "%s punktide kasutamine andmebaasi nimes pole lubatud", + "You need to enter details of an existing account." : "Sa pead sisestama olemasoleva konto andmed.", + "Oracle connection could not be established" : "Ei suuda luua ühendust Oracle baasiga", + "Oracle username and/or password not valid" : "Oracle kasutajatunnus ja/või parool pole õiged", + "PostgreSQL username and/or password not valid" : "PostgreSQL kasutajatunnus ja/või parool pole õiged", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X ei ole toetatud ja %s ei pruugi korralikult toimida sellel platvormil. Kasuta seda omal vastutusel!", + "For the best results, please consider using a GNU/Linux server instead." : "Parema tulemuse saavitamiseks palun kaalu serveris GNU/Linux kasutamist.", + "Set an admin username." : "Määra admin kasutajanimi.", + "Set an admin password." : "Määra admini parool.", + "Can't create or write into the data directory %s" : "Ei suuda luua või kirjutada andmete kataloogi %s", + "Invalid Federated Cloud ID" : "Vigane liitpilve ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Jagamise tagarakend %s peab kasutusele võtma OCP\\Share_Backend liidese", + "Sharing backend %s not found" : "Jagamise tagarakendit %s ei leitud", + "Sharing backend for %s not found" : "Jagamise tagarakendit %s jaoks ei leitud", + "Open »%s«" : "Ava »%s«", + "You are not allowed to share %s" : "Sul pole lubatud %s jagada", + "Can’t increase permissions of %s" : "Ei saa %s õigusi suurendada", + "Files can’t be shared with delete permissions" : "Faile ei saa jagada kustutamise õigusega", + "Files can’t be shared with create permissions" : "Faile ei saa jagada loomise õigusega", + "Expiration date is in the past" : "Aegumise kuupäev on minevikus", + "Can’t set expiration date more than %s days in the future" : "Ei sa määrata aegumise kuupäeva rohkem kui %s päeva tulevikus", + "Click the button below to open it." : "Vajuta allolevat nuppu, et see avada.", + "The requested share does not exist anymore" : "Soovitud jagamist enam ei eksisteeri", + "Could not find category \"%s\"" : "Ei leia kategooriat \"%s\"", + "Sunday" : "Pühapäev", + "Monday" : "Esmaspäev", + "Tuesday" : "Teisipäev", + "Wednesday" : "Kolmapäev", + "Thursday" : "Neljapäev", + "Friday" : "Reede", + "Saturday" : "Laupäev", + "Sun." : "P", + "Mon." : "E", + "Tue." : "T", + "Wed." : "K", + "Thu." : "N", + "Fri." : "R", + "Sat." : "L", + "Su" : "P", + "Mo" : "E", + "Tu" : "T", + "We" : "K", + "Th" : "N", + "Fr" : "R", + "Sa" : "L", + "January" : "Jaanuar", + "February" : "Veebruar", + "March" : "Märts", + "April" : "Aprill", + "May" : "Mai", + "June" : "Juuni", + "July" : "Juuli", + "August" : "August", + "September" : "September", + "October" : "Oktoober", + "November" : "November", + "December" : "Detsember", + "Jan." : "Jaan.", + "Feb." : "Veebr.", + "Mar." : "Märts.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Juuni.", + "Jul." : "Juuli.", + "Aug." : "Aug.", + "Sep." : "Sept.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dets.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kasutajanimes on lubatud ainult järgmised sümbolid: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Sisesta nõuetele vastav kasutajatunnus", + "Username contains whitespace at the beginning or at the end" : "Kasutajanime alguses või lõpus on tühik", + "Username must not consist of dots only" : "Kasutajanimi ei tohi koosneda ainult punktidest", + "A valid password must be provided" : "Sisesta nõuetele vastav parool", + "The username is already being used" : "Kasutajanimi on juba kasutuses", + "Could not create user" : "Ei saanud kasutajat luua", + "User disabled" : "Kasutaja deaktiveeritud", + "a safe home for all your data" : "turvaline koht sinu andmetele", + "File is currently busy, please try again later" : "Fail on hetkel kasutuses, proovi hiljem uuesti", + "Can't read file" : "Faili lugemine ebaõnnestus", + "Application is not enabled" : "Rakendus pole sisse lülitatud", + "Authentication error" : "Autentimise viga", + "Token expired. Please reload page." : "Kontrollkood aegus. Paelun lae leht uuesti.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ühtegi andmebaasi (sqlite, mysql või postgresql) draiverit pole paigaldatud.", + "Cannot write into \"config\" directory" : "Ei saa kirjutada \"config\" kataloogi", + "Cannot write into \"apps\" directory" : "Ei saa kirjutada \"apps\" kataloogi!", + "Cannot create \"data\" directory" : "Ei suuda luua \"data\" kataloogi", + "Setting locale to %s failed" : "Lokaadi %s määramine ebaõnnestus.", + "Please install one of these locales on your system and restart your webserver." : "Palun paigalda mõni neist lokaatides oma süsteemi ning taaskäivita veebiserver.", + "PHP module %s not installed." : "PHP moodulit %s pole paigaldatud.", + "Please ask your server administrator to install the module." : "Palu oma serveri haldajal moodul paigadalda.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "See on tõenäoliselt põhjustatud puhver/kiirendist nagu Zend OPcache või eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moodulid on paigaldatud, kuid neid näitatakse endiselt kui puuduolevad?", + "Please ask your server administrator to restart the web server." : "Palu oma serveri haldajal veebiserver taaskäivitada.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 on nõutav", + "Please upgrade your database version" : "Palun uuenda oma andmebaasi versiooni", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Palun muuda kataloogi õigused 0770-ks, et kataloogi sisu poleks teistele kasutajatele nähtav", + "Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust \"%s\".", + "Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav", + "Following databases are supported: %s" : "Toetatud on järgnevad andmebaasid: %s", + "Following platforms are supported: %s" : "Toetatud on järgnevad platformid: %s", + "Overview" : "Ülevaade", + "Basic settings" : "Põhiseaded", + "Sharing" : "Jagamine", + "Security" : "Turvalisus", + "Groupware" : "Grupitöö", + "Personal info" : "Isiklik info", + "Mobile & desktop" : "Mobiil ja töölaud" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/eu.js b/docker/overlays/nextcloud/html/lib/l10n/eu.js new file mode 100644 index 0000000..4717ca8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/eu.js @@ -0,0 +1,201 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Ezin da idatzi \"config\" karpetan!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Hau normalean konpondu daitekesweb zerbitzarira config karpetan idazteko baimenak emanez", + "See %s" : "Ikusi %s", + "Sample configuration detected" : "Adibide-ezarpena detektatua", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detektatu da adibide-ezarpena kopiatu dela. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.", + "%1$s and %2$s" : "%1$s eta %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s eta %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s eta %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s eta %5$s", + "Education Edition" : "Hezkuntza edizioa", + "Enterprise bundle" : "Enpresa multzoa", + "Groupware bundle" : "Talderanerako multzoa", + "Social sharing bundle" : "Partekatze sozial multzoa", + "PHP %s or higher is required." : "PHP %s edo berriagoa behar da.", + "PHP with a version lower than %s is required." : "PHPren bertsioa %s baino txikiagoa izan behar da.", + "%sbit or higher PHP required." : "%sbiteko edo PHP bertsio berriagoa behar da. ", + "The following databases are supported: %s" : "Hurrengo datu-baseak onartzen dira: %s", + "The command line tool %s could not be found" : "Komando lerroko %s tresna ezin da aurkitu", + "The library %s is not available." : "%s liburutegia ez dago eskuragarri.", + "The following platforms are supported: %s" : "Hurrengo plataformak onartzen dira: %s", + "Server version %s or higher is required." : "Zerbitzariaren %s bertsioa edo berriagoa behar da.", + "Server version %s or lower is required." : "Zerbitzariaren %s bertsioa edo zaharragoa behar da.", + "Logged in user must be an admin" : "Saioa hasitako erabiltzailea administratzailea izan behar da", + "Authentication" : "Autentifikazioa", + "Unknown filetype" : "Fitxategi mota ezezaguna", + "Invalid image" : "Baliogabeko irudia", + "Avatar image is not square" : "Abatarreko irudia ez da karratua", + "today" : "gaur", + "tomorrow" : "bihar", + "yesterday" : "atzo", + "_in %n day_::_in %n days_" : ["egun %nean","%n egunetan"], + "_%n day ago_::_%n days ago_" : ["orain dela egun %n","orain dela %n egun"], + "next month" : "datorren hilabetea", + "last month" : "joan den hilabetean", + "_in %n month_::_in %n months_" : ["hilabete %nean","%n hilabetetan"], + "_%n month ago_::_%n months ago_" : ["orain dela hilabete %n","orain dela %n hilabete"], + "next year" : "datorren urtea", + "last year" : "joan den urtean", + "_in %n year_::_in %n years_" : ["urte %nean","%n urtetan"], + "_%n year ago_::_%n years ago_" : ["orain dela urte %n","orain dela %n urte"], + "_in %n hour_::_in %n hours_" : ["ordu %n barru","%n ordu barru"], + "_%n hour ago_::_%n hours ago_" : ["orain dela ordu %n","orain dela %n ordu"], + "_in %n minute_::_in %n minutes_" : ["minutu %nean","%n minututan"], + "_%n minute ago_::_%n minutes ago_" : ["orain dela minutu %n","orain dela %n minutu"], + "in a few seconds" : "segundo gutxitan", + "seconds ago" : "duela segundu batzuk", + "Empty file" : "Fitxategi hutsa", + "File name is a reserved word" : "Fitxategi izena hitz erreserbatua da", + "File name contains at least one invalid character" : "Fitxategi izenak karaktere baliogabe bat du gutxienez ", + "File name is too long" : "Fitxategi-izena luzeegia da", + "Dot files are not allowed" : "Dot fitxategiak ez dira onartzen", + "Empty filename is not allowed" : "Fitxategiaren izena izin da hutsa izan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "«%s» aplikazioa ezin da instalatu appinfo fitxategia ezin delako irakurri.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" aplikazioa ezin da instalatu ez delako zerbitzariaren bertsio honekin bateragarria.", + "__language_name__" : "Euskara", + "This is an automatically sent email, please do not reply." : "Hau automatikoki bidalitako e-posta bat da, ez erantzun mesedez.", + "Help" : "Laguntza", + "Apps" : "Aplikazioak", + "Settings" : "Ezarpenak", + "Log out" : "Amaitu saioa", + "Users" : "Erabiltzaileak", + "Unknown user" : "Erabiltzaile ezezaguna", + "Additional settings" : "Ezarpen gehiago", + "%s enter the database username and name." : "%s sartu datu-basearen erabiltzaile-izena eta izena.", + "%s enter the database username." : "%s sartu datu basearen erabiltzaile izena.", + "%s enter the database name." : "%s sartu datu basearen izena.", + "%s you may not use dots in the database name" : "%s ezin duzu punturik erabili datu basearen izenean.", + "MySQL username and/or password not valid" : "MySQL erabiltzaile-izen edota pasahitza baliogabea", + "You need to enter details of an existing account." : "Existitzen den kontu baten xehetasunak sartu behar dituzu.", + "Oracle connection could not be established" : "Ezin da Oracle konexioa sortu", + "Oracle username and/or password not valid" : "Oracle erabiltzaile edota pasahitza ez dira egokiak.", + "PostgreSQL username and/or password not valid" : "PostgreSQL erabiltzaile edota pasahitza ez dira egokiak.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X-ek ez du sostengurik eta %s gaizki ibili daiteke plataforma honetan. Erabiltzekotan, zure ardurapean.", + "For the best results, please consider using a GNU/Linux server instead." : "Emaitza hobeak izateko, mesedez gogoan hartu GNU/Linux zerbitzari bat erabiltzea.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Badirudi %s instantzia hau 32 biteko PHP ingurune bat exekutatzen ari dela eta open_basedir aldagaia php.ini fitxategian konfiguratu dela. Honek arazoak sortuko ditu 4 GB baino gehiagoko fitxategiekin eta ez da gomendatzen.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Mesedez kendu open_basedir ezarpena zure php.ini-tik edo aldatu 64-biteko PHPra.", + "Set an admin username." : "Ezarri administraziorako erabiltzaile izena.", + "Set an admin password." : "Ezarri administraziorako pasahitza.", + "Can't create or write into the data directory %s" : "Ezin da %s datu karpeta sortu edo bertan idatzi ", + "Invalid Federated Cloud ID" : "Federatutako Hodei ID ezegokia", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "%s elkarbanaketa motorra OCP\\Share_Backend interfazea inplementatu behar du ", + "Sharing backend %s not found" : "Ez da %s elkarbanaketa motorra aurkitu", + "Sharing backend for %s not found" : "Ez da %srako elkarbanaketa motorrik aurkitu", + "Open »%s«" : "Ireki »%s«", + "%1$s via %2$s" : "%2$s bidez, %1$s", + "You are not allowed to share %s" : "Ez zadue %s elkarbanatzeko baimendua", + "Can’t increase permissions of %s" : "Ezin dira %s(r)en baimenak handitu", + "Files can’t be shared with delete permissions" : "Fitxategiak ezin dira ezabatze baimenarekin partekatu", + "Expiration date is in the past" : "Iraungitze-data iraganean dago", + "Can’t set expiration date more than %s days in the future" : "Ezin da iraungitze-data etorkizunean %s egun baino gehiagora jarri", + "%1$s shared »%2$s« with you" : "%1$serabiltzaileak »%2$s« partekatu du zurekin", + "%1$s shared »%2$s« with you." : "%1$serabiltzaileak »%2$s« partekatu du zurekin", + "Click the button below to open it." : "Egin klik beheko botoian hura irekitzeko", + "The requested share does not exist anymore" : "Eskatutako partekatzea ez da existitzen dagoeneko", + "Could not find category \"%s\"" : "Ezin da \"%s\" kategoria aurkitu", + "Sunday" : "Igandea", + "Monday" : "Astelehena", + "Tuesday" : "Asteartea", + "Wednesday" : "Asteazkena", + "Thursday" : "Osteguna", + "Friday" : "Ostirala", + "Saturday" : "Larunbata", + "Sun." : "Ig.", + "Mon." : "Al.", + "Tue." : "Ar.", + "Wed." : "Az.", + "Thu." : "Og.", + "Fri." : "Ol.", + "Sat." : "Lr.", + "Su" : "Ig", + "Mo" : "Al", + "Tu" : "Ar", + "We" : "Az", + "Th" : "Og", + "Fr" : "Ol", + "Sa" : "Lr", + "January" : "Urtarrila", + "February" : "Otsaila", + "March" : "Martxoa", + "April" : "Apirila", + "May" : "Maiatza", + "June" : "Ekaina", + "July" : "Uztaila", + "August" : "Abuztua", + "September" : "Iraila", + "October" : "Urria", + "November" : "Azaroa", + "December" : "Abendua", + "Jan." : "Urt.", + "Feb." : "Ots.", + "Mar." : "Mar.", + "Apr." : "Api.", + "May." : "Mai.", + "Jun." : "Eka.", + "Jul." : "Uzt.", + "Aug." : "Abu.", + "Sep." : "Ira.", + "Oct." : "Urr.", + "Nov." : "Aza.", + "Dec." : "Abe.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Erabiltzaile-izenean karaktere hauek soilik erabili daitezke: \"a-z\", \"A-Z\", \"0-9\", eta \"_.@-'\"", + "A valid username must be provided" : "Baliozko erabiltzaile izena eman behar da", + "Username contains whitespace at the beginning or at the end" : "Erabiltzaile-izenak zuriuneren bat du hasieran edo amaieran", + "Username must not consist of dots only" : "Erabiltzaile-izena ezin da puntuz osatuta soilik egon", + "Username is invalid because files already exist for this user" : "Erabiltzaile-izena ez da baliozkoa fitxategiak erabiltzaile honentzat existitzen direlako dagoeneko", + "A valid password must be provided" : "Baliozko pasahitza eman behar da", + "The username is already being used" : "Erabiltzaile izena dagoeneko erabiltzen ari da", + "Could not create user" : "Ezin izan da erabiltzailea sortu", + "User disabled" : "Erabiltzaile desgaituta", + "Login canceled by app" : "Aplikazioa saioa bertan behera utzi du", + "a safe home for all your data" : "zure datu guztientzako toki segurua", + "File is currently busy, please try again later" : "Fitxategia lanpetuta dago, saiatu berriro geroago", + "Can't read file" : "Ezin da fitxategia irakurri", + "Application is not enabled" : "Aplikazioa ez dago gaituta", + "Authentication error" : "Autentifikazio errorea", + "Token expired. Please reload page." : "Tokena iraungitu da. Mesedez birkargatu orria.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ez dago datubaseen (sqlite, mysql edo postgresql) driverrik instalatuta.", + "Cannot write into \"config\" directory" : "Ezin da idatzi \"config\" karpetan", + "Cannot write into \"apps\" directory" : "Ezin da idatzi \"apps\" karpetan", + "Cannot create \"data\" directory" : "Ezin da \"data\" karpeta sortu", + "Setting locale to %s failed" : "Lokala %sra ezartzeak huts egin du", + "Please install one of these locales on your system and restart your webserver." : "Instalatu hauetako lokal bat zure sisteman eta berrabiarazi zure web zerbitzaria.", + "PHP module %s not installed." : "PHPren %s modulua ez dago instalaturik.", + "Please ask your server administrator to install the module." : "Mesedez eskatu zure zerbitzariaren kudeatzaileari modulua instala dezan.", + "PHP setting \"%s\" is not set to \"%s\"." : "\"%s\" PHP ezarpena ez dago \"%s\" gisa jarrita.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ezarpen hau php.ini fitxategian doitzen bada, Nextcloud berriro exekutatuko da", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload \"%s\"-(e)ra ezarrita dago \"0\" itxarondako balioaren ordez", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Arazo hau konpontzeko ezarri mbstring.func_overload 0-ra zure php.ini fitxategian", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 bertsioa edo berriagoa behar da. Orain %s dago instalatuta.", + "To fix this issue update your libxml2 version and restart your web server." : "Arazo hori konpontzeko, eguneratu zure libxml2 bertsioa eta berrabiarazi web zerbitzaria.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP lerro bakarreko blokeak mozteko konfiguratua dagoela dirudi. Oinarrizko app batzuk eskuraezin bihurtuko dira.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Hau ziur aski cache/accelerator batek eragin du, hala nola Zend OPcache edo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduluak instalatu dira, baina oraindik faltan bezala markatuta daude?", + "Please ask your server administrator to restart the web server." : "Mesedez eskatu zerbitzariaren kudeatzaileari web zerbitzaria berrabiarazteko.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 behar da", + "Please upgrade your database version" : "Mesedez eguneratu zure datu basearen bertsioa", + "Your data directory is readable by other users" : "Zure datuen karpeta beste erabiltzaileek irakur dezakete", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Mesedez aldatu baimenak 0770ra beste erabiltzaileek karpetan sartu ezin izateko.", + "Your data directory must be an absolute path" : "Zure datuen karpeta bide-izen absolutua izan behar da", + "Check the value of \"datadirectory\" in your configuration" : "Egiaztatu «datadirectory» aldagaiaren balioa zure konfigurazioan", + "Your data directory is invalid" : "Zure datuen karpeta baliogabea da", + "Could not obtain lock type %d on \"%s\"." : "Ezin da lortu sarraia mota %d \"%s\"-an.", + "Storage unauthorized. %s" : "Biltegiratzea ez dago baimenduta. %s", + "Storage incomplete configuration. %s" : "Biltegiratzea ez da osorik konfiguratu. %s", + "Storage connection error. %s" : "Biltegiratze-konexioaren errorea. %s", + "Storage is temporarily not available" : "Biltegia ez dago erabilgarri aldi baterako", + "Storage connection timeout. %s" : "Biltegiratze-konexioa denboraz kanpo geratu da. %s", + "Following databases are supported: %s" : "Hurrengo datubaseak onartzen dira: %s", + "Following platforms are supported: %s" : "Hurrengo plataformak onartzen dira: %s", + "Overview" : "Ikuspegi orokorra", + "Basic settings" : "Oinarrizko ezarpenak", + "Sharing" : "Partekatze", + "Security" : "Segurtasuna", + "Groupware" : "Taldelanerako tresnak", + "Personal info" : "Informazio pertsonala", + "Mobile & desktop" : "Mugikorra eta mahaigaina" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/eu.json b/docker/overlays/nextcloud/html/lib/l10n/eu.json new file mode 100644 index 0000000..643d103 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/eu.json @@ -0,0 +1,199 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Ezin da idatzi \"config\" karpetan!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Hau normalean konpondu daitekesweb zerbitzarira config karpetan idazteko baimenak emanez", + "See %s" : "Ikusi %s", + "Sample configuration detected" : "Adibide-ezarpena detektatua", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detektatu da adibide-ezarpena kopiatu dela. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.", + "%1$s and %2$s" : "%1$s eta %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s eta %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s eta %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s eta %5$s", + "Education Edition" : "Hezkuntza edizioa", + "Enterprise bundle" : "Enpresa multzoa", + "Groupware bundle" : "Talderanerako multzoa", + "Social sharing bundle" : "Partekatze sozial multzoa", + "PHP %s or higher is required." : "PHP %s edo berriagoa behar da.", + "PHP with a version lower than %s is required." : "PHPren bertsioa %s baino txikiagoa izan behar da.", + "%sbit or higher PHP required." : "%sbiteko edo PHP bertsio berriagoa behar da. ", + "The following databases are supported: %s" : "Hurrengo datu-baseak onartzen dira: %s", + "The command line tool %s could not be found" : "Komando lerroko %s tresna ezin da aurkitu", + "The library %s is not available." : "%s liburutegia ez dago eskuragarri.", + "The following platforms are supported: %s" : "Hurrengo plataformak onartzen dira: %s", + "Server version %s or higher is required." : "Zerbitzariaren %s bertsioa edo berriagoa behar da.", + "Server version %s or lower is required." : "Zerbitzariaren %s bertsioa edo zaharragoa behar da.", + "Logged in user must be an admin" : "Saioa hasitako erabiltzailea administratzailea izan behar da", + "Authentication" : "Autentifikazioa", + "Unknown filetype" : "Fitxategi mota ezezaguna", + "Invalid image" : "Baliogabeko irudia", + "Avatar image is not square" : "Abatarreko irudia ez da karratua", + "today" : "gaur", + "tomorrow" : "bihar", + "yesterday" : "atzo", + "_in %n day_::_in %n days_" : ["egun %nean","%n egunetan"], + "_%n day ago_::_%n days ago_" : ["orain dela egun %n","orain dela %n egun"], + "next month" : "datorren hilabetea", + "last month" : "joan den hilabetean", + "_in %n month_::_in %n months_" : ["hilabete %nean","%n hilabetetan"], + "_%n month ago_::_%n months ago_" : ["orain dela hilabete %n","orain dela %n hilabete"], + "next year" : "datorren urtea", + "last year" : "joan den urtean", + "_in %n year_::_in %n years_" : ["urte %nean","%n urtetan"], + "_%n year ago_::_%n years ago_" : ["orain dela urte %n","orain dela %n urte"], + "_in %n hour_::_in %n hours_" : ["ordu %n barru","%n ordu barru"], + "_%n hour ago_::_%n hours ago_" : ["orain dela ordu %n","orain dela %n ordu"], + "_in %n minute_::_in %n minutes_" : ["minutu %nean","%n minututan"], + "_%n minute ago_::_%n minutes ago_" : ["orain dela minutu %n","orain dela %n minutu"], + "in a few seconds" : "segundo gutxitan", + "seconds ago" : "duela segundu batzuk", + "Empty file" : "Fitxategi hutsa", + "File name is a reserved word" : "Fitxategi izena hitz erreserbatua da", + "File name contains at least one invalid character" : "Fitxategi izenak karaktere baliogabe bat du gutxienez ", + "File name is too long" : "Fitxategi-izena luzeegia da", + "Dot files are not allowed" : "Dot fitxategiak ez dira onartzen", + "Empty filename is not allowed" : "Fitxategiaren izena izin da hutsa izan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "«%s» aplikazioa ezin da instalatu appinfo fitxategia ezin delako irakurri.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" aplikazioa ezin da instalatu ez delako zerbitzariaren bertsio honekin bateragarria.", + "__language_name__" : "Euskara", + "This is an automatically sent email, please do not reply." : "Hau automatikoki bidalitako e-posta bat da, ez erantzun mesedez.", + "Help" : "Laguntza", + "Apps" : "Aplikazioak", + "Settings" : "Ezarpenak", + "Log out" : "Amaitu saioa", + "Users" : "Erabiltzaileak", + "Unknown user" : "Erabiltzaile ezezaguna", + "Additional settings" : "Ezarpen gehiago", + "%s enter the database username and name." : "%s sartu datu-basearen erabiltzaile-izena eta izena.", + "%s enter the database username." : "%s sartu datu basearen erabiltzaile izena.", + "%s enter the database name." : "%s sartu datu basearen izena.", + "%s you may not use dots in the database name" : "%s ezin duzu punturik erabili datu basearen izenean.", + "MySQL username and/or password not valid" : "MySQL erabiltzaile-izen edota pasahitza baliogabea", + "You need to enter details of an existing account." : "Existitzen den kontu baten xehetasunak sartu behar dituzu.", + "Oracle connection could not be established" : "Ezin da Oracle konexioa sortu", + "Oracle username and/or password not valid" : "Oracle erabiltzaile edota pasahitza ez dira egokiak.", + "PostgreSQL username and/or password not valid" : "PostgreSQL erabiltzaile edota pasahitza ez dira egokiak.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X-ek ez du sostengurik eta %s gaizki ibili daiteke plataforma honetan. Erabiltzekotan, zure ardurapean.", + "For the best results, please consider using a GNU/Linux server instead." : "Emaitza hobeak izateko, mesedez gogoan hartu GNU/Linux zerbitzari bat erabiltzea.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Badirudi %s instantzia hau 32 biteko PHP ingurune bat exekutatzen ari dela eta open_basedir aldagaia php.ini fitxategian konfiguratu dela. Honek arazoak sortuko ditu 4 GB baino gehiagoko fitxategiekin eta ez da gomendatzen.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Mesedez kendu open_basedir ezarpena zure php.ini-tik edo aldatu 64-biteko PHPra.", + "Set an admin username." : "Ezarri administraziorako erabiltzaile izena.", + "Set an admin password." : "Ezarri administraziorako pasahitza.", + "Can't create or write into the data directory %s" : "Ezin da %s datu karpeta sortu edo bertan idatzi ", + "Invalid Federated Cloud ID" : "Federatutako Hodei ID ezegokia", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "%s elkarbanaketa motorra OCP\\Share_Backend interfazea inplementatu behar du ", + "Sharing backend %s not found" : "Ez da %s elkarbanaketa motorra aurkitu", + "Sharing backend for %s not found" : "Ez da %srako elkarbanaketa motorrik aurkitu", + "Open »%s«" : "Ireki »%s«", + "%1$s via %2$s" : "%2$s bidez, %1$s", + "You are not allowed to share %s" : "Ez zadue %s elkarbanatzeko baimendua", + "Can’t increase permissions of %s" : "Ezin dira %s(r)en baimenak handitu", + "Files can’t be shared with delete permissions" : "Fitxategiak ezin dira ezabatze baimenarekin partekatu", + "Expiration date is in the past" : "Iraungitze-data iraganean dago", + "Can’t set expiration date more than %s days in the future" : "Ezin da iraungitze-data etorkizunean %s egun baino gehiagora jarri", + "%1$s shared »%2$s« with you" : "%1$serabiltzaileak »%2$s« partekatu du zurekin", + "%1$s shared »%2$s« with you." : "%1$serabiltzaileak »%2$s« partekatu du zurekin", + "Click the button below to open it." : "Egin klik beheko botoian hura irekitzeko", + "The requested share does not exist anymore" : "Eskatutako partekatzea ez da existitzen dagoeneko", + "Could not find category \"%s\"" : "Ezin da \"%s\" kategoria aurkitu", + "Sunday" : "Igandea", + "Monday" : "Astelehena", + "Tuesday" : "Asteartea", + "Wednesday" : "Asteazkena", + "Thursday" : "Osteguna", + "Friday" : "Ostirala", + "Saturday" : "Larunbata", + "Sun." : "Ig.", + "Mon." : "Al.", + "Tue." : "Ar.", + "Wed." : "Az.", + "Thu." : "Og.", + "Fri." : "Ol.", + "Sat." : "Lr.", + "Su" : "Ig", + "Mo" : "Al", + "Tu" : "Ar", + "We" : "Az", + "Th" : "Og", + "Fr" : "Ol", + "Sa" : "Lr", + "January" : "Urtarrila", + "February" : "Otsaila", + "March" : "Martxoa", + "April" : "Apirila", + "May" : "Maiatza", + "June" : "Ekaina", + "July" : "Uztaila", + "August" : "Abuztua", + "September" : "Iraila", + "October" : "Urria", + "November" : "Azaroa", + "December" : "Abendua", + "Jan." : "Urt.", + "Feb." : "Ots.", + "Mar." : "Mar.", + "Apr." : "Api.", + "May." : "Mai.", + "Jun." : "Eka.", + "Jul." : "Uzt.", + "Aug." : "Abu.", + "Sep." : "Ira.", + "Oct." : "Urr.", + "Nov." : "Aza.", + "Dec." : "Abe.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Erabiltzaile-izenean karaktere hauek soilik erabili daitezke: \"a-z\", \"A-Z\", \"0-9\", eta \"_.@-'\"", + "A valid username must be provided" : "Baliozko erabiltzaile izena eman behar da", + "Username contains whitespace at the beginning or at the end" : "Erabiltzaile-izenak zuriuneren bat du hasieran edo amaieran", + "Username must not consist of dots only" : "Erabiltzaile-izena ezin da puntuz osatuta soilik egon", + "Username is invalid because files already exist for this user" : "Erabiltzaile-izena ez da baliozkoa fitxategiak erabiltzaile honentzat existitzen direlako dagoeneko", + "A valid password must be provided" : "Baliozko pasahitza eman behar da", + "The username is already being used" : "Erabiltzaile izena dagoeneko erabiltzen ari da", + "Could not create user" : "Ezin izan da erabiltzailea sortu", + "User disabled" : "Erabiltzaile desgaituta", + "Login canceled by app" : "Aplikazioa saioa bertan behera utzi du", + "a safe home for all your data" : "zure datu guztientzako toki segurua", + "File is currently busy, please try again later" : "Fitxategia lanpetuta dago, saiatu berriro geroago", + "Can't read file" : "Ezin da fitxategia irakurri", + "Application is not enabled" : "Aplikazioa ez dago gaituta", + "Authentication error" : "Autentifikazio errorea", + "Token expired. Please reload page." : "Tokena iraungitu da. Mesedez birkargatu orria.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ez dago datubaseen (sqlite, mysql edo postgresql) driverrik instalatuta.", + "Cannot write into \"config\" directory" : "Ezin da idatzi \"config\" karpetan", + "Cannot write into \"apps\" directory" : "Ezin da idatzi \"apps\" karpetan", + "Cannot create \"data\" directory" : "Ezin da \"data\" karpeta sortu", + "Setting locale to %s failed" : "Lokala %sra ezartzeak huts egin du", + "Please install one of these locales on your system and restart your webserver." : "Instalatu hauetako lokal bat zure sisteman eta berrabiarazi zure web zerbitzaria.", + "PHP module %s not installed." : "PHPren %s modulua ez dago instalaturik.", + "Please ask your server administrator to install the module." : "Mesedez eskatu zure zerbitzariaren kudeatzaileari modulua instala dezan.", + "PHP setting \"%s\" is not set to \"%s\"." : "\"%s\" PHP ezarpena ez dago \"%s\" gisa jarrita.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ezarpen hau php.ini fitxategian doitzen bada, Nextcloud berriro exekutatuko da", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload \"%s\"-(e)ra ezarrita dago \"0\" itxarondako balioaren ordez", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Arazo hau konpontzeko ezarri mbstring.func_overload 0-ra zure php.ini fitxategian", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 bertsioa edo berriagoa behar da. Orain %s dago instalatuta.", + "To fix this issue update your libxml2 version and restart your web server." : "Arazo hori konpontzeko, eguneratu zure libxml2 bertsioa eta berrabiarazi web zerbitzaria.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP lerro bakarreko blokeak mozteko konfiguratua dagoela dirudi. Oinarrizko app batzuk eskuraezin bihurtuko dira.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Hau ziur aski cache/accelerator batek eragin du, hala nola Zend OPcache edo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduluak instalatu dira, baina oraindik faltan bezala markatuta daude?", + "Please ask your server administrator to restart the web server." : "Mesedez eskatu zerbitzariaren kudeatzaileari web zerbitzaria berrabiarazteko.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 behar da", + "Please upgrade your database version" : "Mesedez eguneratu zure datu basearen bertsioa", + "Your data directory is readable by other users" : "Zure datuen karpeta beste erabiltzaileek irakur dezakete", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Mesedez aldatu baimenak 0770ra beste erabiltzaileek karpetan sartu ezin izateko.", + "Your data directory must be an absolute path" : "Zure datuen karpeta bide-izen absolutua izan behar da", + "Check the value of \"datadirectory\" in your configuration" : "Egiaztatu «datadirectory» aldagaiaren balioa zure konfigurazioan", + "Your data directory is invalid" : "Zure datuen karpeta baliogabea da", + "Could not obtain lock type %d on \"%s\"." : "Ezin da lortu sarraia mota %d \"%s\"-an.", + "Storage unauthorized. %s" : "Biltegiratzea ez dago baimenduta. %s", + "Storage incomplete configuration. %s" : "Biltegiratzea ez da osorik konfiguratu. %s", + "Storage connection error. %s" : "Biltegiratze-konexioaren errorea. %s", + "Storage is temporarily not available" : "Biltegia ez dago erabilgarri aldi baterako", + "Storage connection timeout. %s" : "Biltegiratze-konexioa denboraz kanpo geratu da. %s", + "Following databases are supported: %s" : "Hurrengo datubaseak onartzen dira: %s", + "Following platforms are supported: %s" : "Hurrengo plataformak onartzen dira: %s", + "Overview" : "Ikuspegi orokorra", + "Basic settings" : "Oinarrizko ezarpenak", + "Sharing" : "Partekatze", + "Security" : "Segurtasuna", + "Groupware" : "Taldelanerako tresnak", + "Personal info" : "Informazio pertsonala", + "Mobile & desktop" : "Mugikorra eta mahaigaina" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/fa.js b/docker/overlays/nextcloud/html/lib/l10n/fa.js new file mode 100644 index 0000000..cee1956 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fa.js @@ -0,0 +1,227 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "نمیتوانید داخل دایرکتوری \"config\" تغییراتی ایجاد کنید", + "This can usually be fixed by giving the webserver write access to the config directory" : "این امر معمولاً با دسترسی به نوشتن وب سرور به فهرست تنظیمات قابل حل است", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "یا اگر ترجیح می دهید پرونده config.php را فقط بخوانید ، گزینه \"config_is_read_only\" را در آن تنظیم کنید.", + "See %s" : "مشاهده %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "یا اگر ترجیح می دهید پرونده config.php را فقط بخوانید ، گزینه \"config_is_read_only\" را در آن تنظیم کنید. دیدن%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "فایل های برنامه %1$sبه درستی تعویض نشد. اطمینان حاصل کنید که این یک نسخه سازگار با سرور است.", + "Sample configuration detected" : "فایل پیکربندی نمونه پیدا شد", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تشخیص داده شده است که پیکربندی نمونه کپی شده است. این می تواند نصب شما را خراب کند و پشتیبانی نمی شود. لطفاً قبل از انجام تغییرات در config.php ، اسناد را بخوانید", + "%1$s and %2$s" : "%1$sو%2$s", + "%1$s, %2$s and %3$s" : "%1$s،%2$sو%3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s،%2$s،%3$sو%4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s،%2$s،%3$s،%4$sو%5$s", + "Education Edition" : "نسخه آموزش", + "Enterprise bundle" : "بسته نرم افزاری سازمانی", + "Groupware bundle" : "بسته نرم افزاری گروهی", + "Hub bundle" : "بسته های توپی", + "Social sharing bundle" : "بسته نرم افزاری اشتراک اجتماعی", + "PHP %s or higher is required." : "PHP نسخه‌ی %s یا بالاتر نیاز است.", + "PHP with a version lower than %s is required." : "PHP با نسخه پایین تر از مورد نیاز%s است.", + "%sbit or higher PHP required." : "%sکمی یا بالاتر PHP لازم است.", + "The command line tool %s could not be found" : "ابزار کامندلاین %s پیدا نشد", + "The library %s is not available." : "کتابخانه‌ی %s در دسترس نیست.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%3$sکتابخانه با%1$s نسخه بالاتر از حد مورد%2$s نیاز - نسخه موجود", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%3$sکتابخانه %1$sبا نسخه پایین تر از حد مورد%2$s نیاز - نسخه موجود", + "Server version %s or higher is required." : "نسخه سرور%s یا بالاتر مورد نیاز است.", + "Server version %s or lower is required." : "نسخه سرور%sیا پایین مورد نیاز است.", + "Logged in user must be an admin or sub admin" : "ورود به سیستم کاربر باید یک مدیر یا مدیر فرعی باشد", + "Logged in user must be an admin" : "ورود به سیستم کاربر باید مدیر سایت باشد", + "Wiping of device %s has started" : "پاک کردن دستگاه%s شروع شده است", + "Wiping of device »%s« has started" : "پاک کردن دستگاه%s شروع شده است", + "»%s« started remote wipe" : "%sپاک کردن از راه دور", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "دستگاه یا برنامه%s فرآیند پاک کردن از راه دور را آغاز کرده است. پس از اتمام مراحل ، ایمیل دیگری دریافت خواهید کرد", + "Wiping of device %s has finished" : "پاک کردن دستگاه %sبه پایان رسیده است", + "Wiping of device »%s« has finished" : "پاک کردن دستگاه %sبه پایان رسیده است", + "»%s« finished remote wipe" : "%sپاک کردن از راه دور", + "Device or application »%s« has finished the remote wipe process." : "دستگاه یا برنامه %sفرآیند پاک کردن از راه دور را به پایان رسانده است.", + "Remote wipe started" : "پاک کردن از راه دور شروع شد", + "A remote wipe was started on device %s" : "پاک کردن از راه دور روی دستگاه شروع شد%s", + "Remote wipe finished" : "پاک کردن از راه دور به پایان رسید", + "The remote wipe on %s has finished" : "پاک کردن از راه دور روی%s کار تمام شد", + "Authentication" : "احراز هویت", + "Unknown filetype" : "نوع فایل ناشناخته", + "Invalid image" : "عکس نامعتبر", + "Avatar image is not square" : "تصویر آواتار مربع نیست", + "today" : "امروز", + "tomorrow" : "فردا", + "yesterday" : "دیروز", + "_%n day ago_::_%n days ago_" : ["%n روز پیش","%n روز پیش"], + "next month" : "ماه آینده", + "last month" : "ماه قبل", + "_%n month ago_::_%n months ago_" : ["%n ماه قبل","%n ماه قبل"], + "next year" : "سال آینده", + "last year" : "سال قبل", + "_%n year ago_::_%n years ago_" : ["%n سال پیش","%n سال پیش"], + "_%n hour ago_::_%n hours ago_" : ["%n ساعت قبل","%n ساعت قبل"], + "_%n minute ago_::_%n minutes ago_" : ["%n دقیقه قبل","%n دقیقه قبل"], + "in a few seconds" : "در چند ثانیه", + "seconds ago" : "ثانیه‌ها پیش", + "Empty file" : "پرونده خالی", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ماژول با شناسه:%s وجود ندارد. لطفاً آن را در تنظیمات برنامه خود فعال کنید یا با سرپرست خود تماس بگیرید", + "File name is a reserved word" : "این نام فایل جزو کلمات رزرو می‌باشد", + "File name contains at least one invalid character" : "نام فایل دارای حداقل یک کاراکتر نامعتبر است", + "File name is too long" : "نام فایل خیلی بزرگ است", + "Dot files are not allowed" : "فایل های نقطه مجاز نیستند", + "Empty filename is not allowed" : "نام فایل نمی‌تواند خالی باشد", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "برنامه%s به دلیل نصب پرونده اطلاعات برنامه قابل نصب نیست.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "برنامه%s قابل نصب نیست زیرا با این نسخه از سرور سازگار نیست.", + "__language_name__" : "فارسى", + "This is an automatically sent email, please do not reply." : "این یک ایمیل بصورت خودکار ارسال شده است ، لطفاً پاسخ ندهید.", + "Help" : "کمک", + "Apps" : " برنامه ها", + "Settings" : "تنظیمات", + "Log out" : "خروج", + "Users" : "کاربران", + "Unknown user" : "کاربر نامعلوم", + "Additional settings" : "تنظیمات اضافی", + "%s enter the database username and name." : "نام کاربری و نام پایگاه داده را وارد کنید.%s", + "%s enter the database username." : "%s نام کاربری پایگاه داده را وارد نمایید.", + "%s enter the database name." : "%s نام پایگاه داده را وارد نمایید.", + "%s you may not use dots in the database name" : "%s شما نباید از نقطه در نام پایگاه داده استفاده نمایید.", + "You need to enter details of an existing account." : "شما باید جزئیات یک حساب موجود را وارد کنید.", + "Oracle connection could not be established" : "ارتباط اراکل نمیتواند برقرار باشد.", + "Oracle username and/or password not valid" : "نام کاربری و / یا رمزعبور اراکل معتبر نیست.", + "PostgreSQL username and/or password not valid" : "PostgreSQL نام کاربری و / یا رمزعبور معتبر نیست.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X پشتیبانی نمی شود و %sبه درستی روی این پلتفرم کار نخواهد کرد. از خطر آن استفاده کنید!", + "For the best results, please consider using a GNU/Linux server instead." : "برای بهترین نتیجه ، لطفاً به جای آن از سرور GNU / Linux در نظر بگیرید.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "به نظر می رسد%s که این نمونه در یک محیط PHP 32 بیتی در حال اجرا است و open_baseir در php.ini پیکربندی شده است. این مسئله به پرونده هایی با بیش از 4 گیگ منجر می شود و بسیار دلسرد می شود", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "لطفاً تنظیمات open_baseir را درون php.ini خود حذف کنید یا به PHP 64 بیتی تغییر دهید.", + "Set an admin username." : "یک نام کاربری برای مدیر تنظیم نمایید.", + "Set an admin password." : "یک رمزعبور برای مدیر تنظیم نمایید.", + "Can't create or write into the data directory %s" : "%sنمی توانید در فهرست داده ایجاد یا نوشتن کنید", + "Invalid Federated Cloud ID" : "شناسه فدرال شده با ابر Cloud", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "به اشتراک گذاشتن باطن باید رابط OCP \\ Share_Backend %sرا پیاده سازی کند", + "Sharing backend %s not found" : "به اشتراک گذاشتن باطن%s یافت نشد", + "Sharing backend for %s not found" : "به اشتراک گذاشتن باطن برای%s یافت نشد", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s به اشتراک گذاشته شده »%2$s« با شماست و می خواهد اضافه کند:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s به اشتراک گذاشته شده »%2$s« با شماست و می خواهد اضافه کند:", + "»%s« added a note to a file shared with you" : "»%s« یادداشتی را به پرونده ای که با شما به اشتراک گذاشته شده است اضافه کرد", + "Open »%s«" : "باز کن »%s«", + "%1$s via %2$s" : "%1$s از طریق %2$s", + "You are not allowed to share %s" : "شما مجاز به اشتراک گذاری نیستید%s", + "Can’t increase permissions of %s" : "مجوزها را نمی توان افزایش داد%s", + "Files can’t be shared with delete permissions" : "فایلها را نمی توان با مجوزهای حذف حذف کرد", + "Files can’t be shared with create permissions" : "فایلها را نمی توان با ایجاد مجوزها به اشتراک گذاشت", + "Expiration date is in the past" : "تاریخ انقضا در گذشته است", + "Can’t set expiration date more than %s days in the future" : "نمی توان تاریخ انقضا را بیش%s از روزها در آینده تعیین کرد", + "%1$s shared »%2$s« with you" : "%1$s به اشتراک گذاشته » %2$s« با شما", + "%1$s shared »%2$s« with you." : "%1$s به اشتراک گذاشته » %2$s« با شما", + "Click the button below to open it." : "برای باز کردن آن روی دکمه زیر کلیک کنید.", + "The requested share does not exist anymore" : "سهم درخواست شده دیگر وجود ندارد", + "Could not find category \"%s\"" : "دسته بندی %s یافت نشد", + "Sunday" : "یکشنبه", + "Monday" : "دوشنبه", + "Tuesday" : "سه شنبه", + "Wednesday" : "چهارشنبه", + "Thursday" : "پنجشنبه", + "Friday" : "جمعه", + "Saturday" : "شنبه", + "Sun." : "یکشنبه", + "Mon." : "دوشنبه", + "Tue." : "سه شنبه", + "Wed." : "چهارشنبه", + "Thu." : "پنج شنبه", + "Fri." : "جمعه", + "Sat." : "شنبه", + "Su" : "یک‌شنبه", + "Mo" : "دوشنبه", + "Tu" : "سه‌شنبه", + "We" : "چهار‌شنبه", + "Th" : "پنج‌شنبه", + "Fr" : "جمعه", + "Sa" : "شنبه", + "January" : "ژانویه", + "February" : "فبریه", + "March" : "مارس", + "April" : "آوریل", + "May" : "می", + "June" : "ژوئن", + "July" : "جولای", + "August" : "آگوست", + "September" : "سپتامبر", + "October" : "اکتبر", + "November" : "نوامبر", + "December" : "دسامبر", + "Jan." : "ژانویه", + "Feb." : "فوریه", + "Mar." : "مارچ", + "Apr." : "آوریل", + "May." : "می", + "Jun." : "ژوئن", + "Jul." : "جولای", + "Aug." : "آگوست", + "Sep." : "سپتامبر", + "Oct." : "اکتبر", + "Nov." : "نوامبر", + "Dec." : "دسامبر", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "فقط کاراکترهای زیر در نام کاربری مجاز هستند: \"a-z\" ، \"A-Z\" ، \"0-9\" و \"_. @ - '\"", + "A valid username must be provided" : "نام کاربری صحیح باید وارد شود", + "Username contains whitespace at the beginning or at the end" : "نام کاربری دارای فضای سفید در ابتدا یا انتهای آن است", + "Username must not consist of dots only" : "نام کاربری نباید فقط از نقاط تشکیل شده باشد", + "A valid password must be provided" : "رمز عبور صحیح باید وارد شود", + "The username is already being used" : "نام‌کاربری قبلا استفاده شده است", + "Could not create user" : "کاربر ایجاد نشد", + "User disabled" : "کاربر غیرفعال", + "Login canceled by app" : "ورود به سیستم توسط برنامه لغو شد", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "%2$sبرنامه %1$sنصب نمی شود زیرا وابستگی های زیر برآورده نشده است:", + "a safe home for all your data" : "خانه ای امن برای تمام داده های شما", + "File is currently busy, please try again later" : "فایل در حال حاضر مشغول است، لطفا مجددا تلاش کنید", + "Can't read file" : "امکان خواندن فایل وجود ندارد", + "Application is not enabled" : "برنامه فعال نشده است", + "Authentication error" : "خطا در اعتبار سنجی", + "Token expired. Please reload page." : "Token منقضی شده است. لطفا دوباره صفحه را بارگذاری نمایید.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "هیچ درایور پایگاه داده (sqlite ، mysql یا postgresql) نصب نشده است.", + "Cannot write into \"config\" directory" : "امکان نوشتن درون شاخه‌ی \"config\" وجود ندارد", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "%sاین امر معمولاً با دسترسی به نوشتن وب سرور به فهرست تنظیمات قابل حل است. دیدن", + "Cannot write into \"apps\" directory" : "نمی توان در فهرست \"برنامه ها\" نوشت", + "Cannot create \"data\" directory" : "دایرکتوری \"داده\" ایجاد نمی شود", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "این امر معمولاً با دسترسی به نوشتن وب سرور به فهرست اصلی قابل حل است. دیدن%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "معمولاً مجوزها می توانند با دسترسی به نوشتن وب سرور به فهرست اصلی ، ثابت شوند. دیدن%s", + "Setting locale to %s failed" : "تنظیم محلی در %sانجام نشد", + "Please install one of these locales on your system and restart your webserver." : "لطفاً یکی از این لوکال ها را روی سیستم خود نصب کرده و شبکه سرور خود را مجدداً راه اندازی کنید.", + "PHP module %s not installed." : "ماژول PHP %s نصب نشده است.", + "Please ask your server administrator to install the module." : "لطفا از مدیر سیستم بخواهید تا ماژول را نصب کند.", + "PHP setting \"%s\" is not set to \"%s\"." : "تنظیمات PHP%s تنظیم نشده است%s", + "Adjusting this setting in php.ini will make Nextcloud run again" : "تنظیم این تنظیمات در php.ini باعث می شود Nextcloud دوباره اجرا شود", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload به جای %sمقدار مورد انتظار تنظیم شده است", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "برای رفع این مشکل ، mbstring.func_overload را 0 در php.ini خود تنظیم کنید", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 حداقل مورد نیاز است. در حال حاضر %sنصب شده است", + "To fix this issue update your libxml2 version and restart your web server." : "برای رفع این مشکل نسخه libxml2 خود را به روز کنید و سرور وب خود را مجدداً راه اندازی کنید.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ظاهراً برای خنثی کردن بلوک های اسناد درون خطی تنظیم شده است. این کار چندین برنامه اصلی را غیرقابل دسترسی خواهد کرد.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "این احتمالاً توسط حافظه پنهان / کش مانند Zend OPcache یا eAccelerator ایجاد شده است.", + "PHP modules have been installed, but they are still listed as missing?" : "ماژول های پی اچ پی نصب شده اند ، اما هنوز هم به عنوان مفقود شده ذکر شده اند؟", + "Please ask your server administrator to restart the web server." : "لطفاً از سرور سرور خود بخواهید که وب سرور را مجدداً راه اندازی کند.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 نیاز است", + "Please upgrade your database version" : "لطفا نسخه‌ی پایگاه‌داده‌ی خود را بروز کنید", + "Your data directory is readable by other users" : "فهرست داده های شما توسط سایر کاربران قابل خواندن است", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "لطفاً مجوزها را به 0770 تغییر دهید تا فهرست توسط سایر کاربران فهرست نشود.", + "Your data directory must be an absolute path" : "فهرست داده های شما باید مسیری مطلق باشد", + "Check the value of \"datadirectory\" in your configuration" : "مقدار \"datadirectory\" را در پیکربندی خود بررسی کنید", + "Your data directory is invalid" : "فهرست داده شما نامعتبر است", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "اطمینان حاصل کنید که فایلی به نام \".ocdata\" در ریشه دایرکتوری داده وجود دارد.", + "Action \"%s\" not supported or implemented." : "عملی%s پشتیبانی یا اجرا نشده است.", + "Authentication failed, wrong token or provider ID given" : "تأیید اعتبار انجام نشد ، نشانه اشتباه یا شناسه ارائه دهنده داده شد", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "پارامترهای موجود برای تکمیل درخواست. پارامترهای موجود نیست%s", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "شناسه%1$s قبلاً توسط ارائه دهنده فدراسیون ابر استفاده شده است%2$s", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ارائه دهنده فدراسیون Cloud با شناسه:%s وجود ندارد.", + "Could not obtain lock type %d on \"%s\"." : "نمی توان نوع%d قفل را به دست آورد%s", + "Storage unauthorized. %s" : "ذخیره سازی غیر مجاز.%s", + "Storage incomplete configuration. %s" : "پیکربندی ناقص ذخیره سازی.%s
", + "Storage connection error. %s" : "خطای اتصال ذخیره سازی%s", + "Storage is temporarily not available" : "ذخیره سازی به طور موقت در دسترس نیست", + "Storage connection timeout. %s" : "مدت زمان اتصال ذخیره سازی%s", + "Following databases are supported: %s" : "پایگاه‌داده‌ های ذکر شده مورد نیاز است: %s", + "Following platforms are supported: %s" : "سیستم عامل های زیر پشتیبانی می شوند%s", + "Overview" : "بررسی اجمالی", + "Basic settings" : "تنظیمات پایه", + "Sharing" : "اشتراک گذاری", + "Security" : "امنیت", + "Groupware" : "گروه های نرم افزاری", + "Personal info" : "مشخصات شخصی", + "Mobile & desktop" : "موبایل و دسک تاپ", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "این امر معمولاً با دسترسی به وب سرور دسترسی به فهرست برنامه ها یا غیرفعال کردن برنامه در پرونده پیکربندی قابل رفع است. دیدن%s" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/fa.json b/docker/overlays/nextcloud/html/lib/l10n/fa.json new file mode 100644 index 0000000..2a33183 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fa.json @@ -0,0 +1,225 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "نمیتوانید داخل دایرکتوری \"config\" تغییراتی ایجاد کنید", + "This can usually be fixed by giving the webserver write access to the config directory" : "این امر معمولاً با دسترسی به نوشتن وب سرور به فهرست تنظیمات قابل حل است", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "یا اگر ترجیح می دهید پرونده config.php را فقط بخوانید ، گزینه \"config_is_read_only\" را در آن تنظیم کنید.", + "See %s" : "مشاهده %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "یا اگر ترجیح می دهید پرونده config.php را فقط بخوانید ، گزینه \"config_is_read_only\" را در آن تنظیم کنید. دیدن%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "فایل های برنامه %1$sبه درستی تعویض نشد. اطمینان حاصل کنید که این یک نسخه سازگار با سرور است.", + "Sample configuration detected" : "فایل پیکربندی نمونه پیدا شد", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تشخیص داده شده است که پیکربندی نمونه کپی شده است. این می تواند نصب شما را خراب کند و پشتیبانی نمی شود. لطفاً قبل از انجام تغییرات در config.php ، اسناد را بخوانید", + "%1$s and %2$s" : "%1$sو%2$s", + "%1$s, %2$s and %3$s" : "%1$s،%2$sو%3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s،%2$s،%3$sو%4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s،%2$s،%3$s،%4$sو%5$s", + "Education Edition" : "نسخه آموزش", + "Enterprise bundle" : "بسته نرم افزاری سازمانی", + "Groupware bundle" : "بسته نرم افزاری گروهی", + "Hub bundle" : "بسته های توپی", + "Social sharing bundle" : "بسته نرم افزاری اشتراک اجتماعی", + "PHP %s or higher is required." : "PHP نسخه‌ی %s یا بالاتر نیاز است.", + "PHP with a version lower than %s is required." : "PHP با نسخه پایین تر از مورد نیاز%s است.", + "%sbit or higher PHP required." : "%sکمی یا بالاتر PHP لازم است.", + "The command line tool %s could not be found" : "ابزار کامندلاین %s پیدا نشد", + "The library %s is not available." : "کتابخانه‌ی %s در دسترس نیست.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%3$sکتابخانه با%1$s نسخه بالاتر از حد مورد%2$s نیاز - نسخه موجود", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%3$sکتابخانه %1$sبا نسخه پایین تر از حد مورد%2$s نیاز - نسخه موجود", + "Server version %s or higher is required." : "نسخه سرور%s یا بالاتر مورد نیاز است.", + "Server version %s or lower is required." : "نسخه سرور%sیا پایین مورد نیاز است.", + "Logged in user must be an admin or sub admin" : "ورود به سیستم کاربر باید یک مدیر یا مدیر فرعی باشد", + "Logged in user must be an admin" : "ورود به سیستم کاربر باید مدیر سایت باشد", + "Wiping of device %s has started" : "پاک کردن دستگاه%s شروع شده است", + "Wiping of device »%s« has started" : "پاک کردن دستگاه%s شروع شده است", + "»%s« started remote wipe" : "%sپاک کردن از راه دور", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "دستگاه یا برنامه%s فرآیند پاک کردن از راه دور را آغاز کرده است. پس از اتمام مراحل ، ایمیل دیگری دریافت خواهید کرد", + "Wiping of device %s has finished" : "پاک کردن دستگاه %sبه پایان رسیده است", + "Wiping of device »%s« has finished" : "پاک کردن دستگاه %sبه پایان رسیده است", + "»%s« finished remote wipe" : "%sپاک کردن از راه دور", + "Device or application »%s« has finished the remote wipe process." : "دستگاه یا برنامه %sفرآیند پاک کردن از راه دور را به پایان رسانده است.", + "Remote wipe started" : "پاک کردن از راه دور شروع شد", + "A remote wipe was started on device %s" : "پاک کردن از راه دور روی دستگاه شروع شد%s", + "Remote wipe finished" : "پاک کردن از راه دور به پایان رسید", + "The remote wipe on %s has finished" : "پاک کردن از راه دور روی%s کار تمام شد", + "Authentication" : "احراز هویت", + "Unknown filetype" : "نوع فایل ناشناخته", + "Invalid image" : "عکس نامعتبر", + "Avatar image is not square" : "تصویر آواتار مربع نیست", + "today" : "امروز", + "tomorrow" : "فردا", + "yesterday" : "دیروز", + "_%n day ago_::_%n days ago_" : ["%n روز پیش","%n روز پیش"], + "next month" : "ماه آینده", + "last month" : "ماه قبل", + "_%n month ago_::_%n months ago_" : ["%n ماه قبل","%n ماه قبل"], + "next year" : "سال آینده", + "last year" : "سال قبل", + "_%n year ago_::_%n years ago_" : ["%n سال پیش","%n سال پیش"], + "_%n hour ago_::_%n hours ago_" : ["%n ساعت قبل","%n ساعت قبل"], + "_%n minute ago_::_%n minutes ago_" : ["%n دقیقه قبل","%n دقیقه قبل"], + "in a few seconds" : "در چند ثانیه", + "seconds ago" : "ثانیه‌ها پیش", + "Empty file" : "پرونده خالی", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ماژول با شناسه:%s وجود ندارد. لطفاً آن را در تنظیمات برنامه خود فعال کنید یا با سرپرست خود تماس بگیرید", + "File name is a reserved word" : "این نام فایل جزو کلمات رزرو می‌باشد", + "File name contains at least one invalid character" : "نام فایل دارای حداقل یک کاراکتر نامعتبر است", + "File name is too long" : "نام فایل خیلی بزرگ است", + "Dot files are not allowed" : "فایل های نقطه مجاز نیستند", + "Empty filename is not allowed" : "نام فایل نمی‌تواند خالی باشد", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "برنامه%s به دلیل نصب پرونده اطلاعات برنامه قابل نصب نیست.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "برنامه%s قابل نصب نیست زیرا با این نسخه از سرور سازگار نیست.", + "__language_name__" : "فارسى", + "This is an automatically sent email, please do not reply." : "این یک ایمیل بصورت خودکار ارسال شده است ، لطفاً پاسخ ندهید.", + "Help" : "کمک", + "Apps" : " برنامه ها", + "Settings" : "تنظیمات", + "Log out" : "خروج", + "Users" : "کاربران", + "Unknown user" : "کاربر نامعلوم", + "Additional settings" : "تنظیمات اضافی", + "%s enter the database username and name." : "نام کاربری و نام پایگاه داده را وارد کنید.%s", + "%s enter the database username." : "%s نام کاربری پایگاه داده را وارد نمایید.", + "%s enter the database name." : "%s نام پایگاه داده را وارد نمایید.", + "%s you may not use dots in the database name" : "%s شما نباید از نقطه در نام پایگاه داده استفاده نمایید.", + "You need to enter details of an existing account." : "شما باید جزئیات یک حساب موجود را وارد کنید.", + "Oracle connection could not be established" : "ارتباط اراکل نمیتواند برقرار باشد.", + "Oracle username and/or password not valid" : "نام کاربری و / یا رمزعبور اراکل معتبر نیست.", + "PostgreSQL username and/or password not valid" : "PostgreSQL نام کاربری و / یا رمزعبور معتبر نیست.", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X پشتیبانی نمی شود و %sبه درستی روی این پلتفرم کار نخواهد کرد. از خطر آن استفاده کنید!", + "For the best results, please consider using a GNU/Linux server instead." : "برای بهترین نتیجه ، لطفاً به جای آن از سرور GNU / Linux در نظر بگیرید.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "به نظر می رسد%s که این نمونه در یک محیط PHP 32 بیتی در حال اجرا است و open_baseir در php.ini پیکربندی شده است. این مسئله به پرونده هایی با بیش از 4 گیگ منجر می شود و بسیار دلسرد می شود", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "لطفاً تنظیمات open_baseir را درون php.ini خود حذف کنید یا به PHP 64 بیتی تغییر دهید.", + "Set an admin username." : "یک نام کاربری برای مدیر تنظیم نمایید.", + "Set an admin password." : "یک رمزعبور برای مدیر تنظیم نمایید.", + "Can't create or write into the data directory %s" : "%sنمی توانید در فهرست داده ایجاد یا نوشتن کنید", + "Invalid Federated Cloud ID" : "شناسه فدرال شده با ابر Cloud", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "به اشتراک گذاشتن باطن باید رابط OCP \\ Share_Backend %sرا پیاده سازی کند", + "Sharing backend %s not found" : "به اشتراک گذاشتن باطن%s یافت نشد", + "Sharing backend for %s not found" : "به اشتراک گذاشتن باطن برای%s یافت نشد", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s به اشتراک گذاشته شده »%2$s« با شماست و می خواهد اضافه کند:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s به اشتراک گذاشته شده »%2$s« با شماست و می خواهد اضافه کند:", + "»%s« added a note to a file shared with you" : "»%s« یادداشتی را به پرونده ای که با شما به اشتراک گذاشته شده است اضافه کرد", + "Open »%s«" : "باز کن »%s«", + "%1$s via %2$s" : "%1$s از طریق %2$s", + "You are not allowed to share %s" : "شما مجاز به اشتراک گذاری نیستید%s", + "Can’t increase permissions of %s" : "مجوزها را نمی توان افزایش داد%s", + "Files can’t be shared with delete permissions" : "فایلها را نمی توان با مجوزهای حذف حذف کرد", + "Files can’t be shared with create permissions" : "فایلها را نمی توان با ایجاد مجوزها به اشتراک گذاشت", + "Expiration date is in the past" : "تاریخ انقضا در گذشته است", + "Can’t set expiration date more than %s days in the future" : "نمی توان تاریخ انقضا را بیش%s از روزها در آینده تعیین کرد", + "%1$s shared »%2$s« with you" : "%1$s به اشتراک گذاشته » %2$s« با شما", + "%1$s shared »%2$s« with you." : "%1$s به اشتراک گذاشته » %2$s« با شما", + "Click the button below to open it." : "برای باز کردن آن روی دکمه زیر کلیک کنید.", + "The requested share does not exist anymore" : "سهم درخواست شده دیگر وجود ندارد", + "Could not find category \"%s\"" : "دسته بندی %s یافت نشد", + "Sunday" : "یکشنبه", + "Monday" : "دوشنبه", + "Tuesday" : "سه شنبه", + "Wednesday" : "چهارشنبه", + "Thursday" : "پنجشنبه", + "Friday" : "جمعه", + "Saturday" : "شنبه", + "Sun." : "یکشنبه", + "Mon." : "دوشنبه", + "Tue." : "سه شنبه", + "Wed." : "چهارشنبه", + "Thu." : "پنج شنبه", + "Fri." : "جمعه", + "Sat." : "شنبه", + "Su" : "یک‌شنبه", + "Mo" : "دوشنبه", + "Tu" : "سه‌شنبه", + "We" : "چهار‌شنبه", + "Th" : "پنج‌شنبه", + "Fr" : "جمعه", + "Sa" : "شنبه", + "January" : "ژانویه", + "February" : "فبریه", + "March" : "مارس", + "April" : "آوریل", + "May" : "می", + "June" : "ژوئن", + "July" : "جولای", + "August" : "آگوست", + "September" : "سپتامبر", + "October" : "اکتبر", + "November" : "نوامبر", + "December" : "دسامبر", + "Jan." : "ژانویه", + "Feb." : "فوریه", + "Mar." : "مارچ", + "Apr." : "آوریل", + "May." : "می", + "Jun." : "ژوئن", + "Jul." : "جولای", + "Aug." : "آگوست", + "Sep." : "سپتامبر", + "Oct." : "اکتبر", + "Nov." : "نوامبر", + "Dec." : "دسامبر", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "فقط کاراکترهای زیر در نام کاربری مجاز هستند: \"a-z\" ، \"A-Z\" ، \"0-9\" و \"_. @ - '\"", + "A valid username must be provided" : "نام کاربری صحیح باید وارد شود", + "Username contains whitespace at the beginning or at the end" : "نام کاربری دارای فضای سفید در ابتدا یا انتهای آن است", + "Username must not consist of dots only" : "نام کاربری نباید فقط از نقاط تشکیل شده باشد", + "A valid password must be provided" : "رمز عبور صحیح باید وارد شود", + "The username is already being used" : "نام‌کاربری قبلا استفاده شده است", + "Could not create user" : "کاربر ایجاد نشد", + "User disabled" : "کاربر غیرفعال", + "Login canceled by app" : "ورود به سیستم توسط برنامه لغو شد", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "%2$sبرنامه %1$sنصب نمی شود زیرا وابستگی های زیر برآورده نشده است:", + "a safe home for all your data" : "خانه ای امن برای تمام داده های شما", + "File is currently busy, please try again later" : "فایل در حال حاضر مشغول است، لطفا مجددا تلاش کنید", + "Can't read file" : "امکان خواندن فایل وجود ندارد", + "Application is not enabled" : "برنامه فعال نشده است", + "Authentication error" : "خطا در اعتبار سنجی", + "Token expired. Please reload page." : "Token منقضی شده است. لطفا دوباره صفحه را بارگذاری نمایید.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "هیچ درایور پایگاه داده (sqlite ، mysql یا postgresql) نصب نشده است.", + "Cannot write into \"config\" directory" : "امکان نوشتن درون شاخه‌ی \"config\" وجود ندارد", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "%sاین امر معمولاً با دسترسی به نوشتن وب سرور به فهرست تنظیمات قابل حل است. دیدن", + "Cannot write into \"apps\" directory" : "نمی توان در فهرست \"برنامه ها\" نوشت", + "Cannot create \"data\" directory" : "دایرکتوری \"داده\" ایجاد نمی شود", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "این امر معمولاً با دسترسی به نوشتن وب سرور به فهرست اصلی قابل حل است. دیدن%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "معمولاً مجوزها می توانند با دسترسی به نوشتن وب سرور به فهرست اصلی ، ثابت شوند. دیدن%s", + "Setting locale to %s failed" : "تنظیم محلی در %sانجام نشد", + "Please install one of these locales on your system and restart your webserver." : "لطفاً یکی از این لوکال ها را روی سیستم خود نصب کرده و شبکه سرور خود را مجدداً راه اندازی کنید.", + "PHP module %s not installed." : "ماژول PHP %s نصب نشده است.", + "Please ask your server administrator to install the module." : "لطفا از مدیر سیستم بخواهید تا ماژول را نصب کند.", + "PHP setting \"%s\" is not set to \"%s\"." : "تنظیمات PHP%s تنظیم نشده است%s", + "Adjusting this setting in php.ini will make Nextcloud run again" : "تنظیم این تنظیمات در php.ini باعث می شود Nextcloud دوباره اجرا شود", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload به جای %sمقدار مورد انتظار تنظیم شده است", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "برای رفع این مشکل ، mbstring.func_overload را 0 در php.ini خود تنظیم کنید", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 حداقل مورد نیاز است. در حال حاضر %sنصب شده است", + "To fix this issue update your libxml2 version and restart your web server." : "برای رفع این مشکل نسخه libxml2 خود را به روز کنید و سرور وب خود را مجدداً راه اندازی کنید.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ظاهراً برای خنثی کردن بلوک های اسناد درون خطی تنظیم شده است. این کار چندین برنامه اصلی را غیرقابل دسترسی خواهد کرد.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "این احتمالاً توسط حافظه پنهان / کش مانند Zend OPcache یا eAccelerator ایجاد شده است.", + "PHP modules have been installed, but they are still listed as missing?" : "ماژول های پی اچ پی نصب شده اند ، اما هنوز هم به عنوان مفقود شده ذکر شده اند؟", + "Please ask your server administrator to restart the web server." : "لطفاً از سرور سرور خود بخواهید که وب سرور را مجدداً راه اندازی کند.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 نیاز است", + "Please upgrade your database version" : "لطفا نسخه‌ی پایگاه‌داده‌ی خود را بروز کنید", + "Your data directory is readable by other users" : "فهرست داده های شما توسط سایر کاربران قابل خواندن است", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "لطفاً مجوزها را به 0770 تغییر دهید تا فهرست توسط سایر کاربران فهرست نشود.", + "Your data directory must be an absolute path" : "فهرست داده های شما باید مسیری مطلق باشد", + "Check the value of \"datadirectory\" in your configuration" : "مقدار \"datadirectory\" را در پیکربندی خود بررسی کنید", + "Your data directory is invalid" : "فهرست داده شما نامعتبر است", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "اطمینان حاصل کنید که فایلی به نام \".ocdata\" در ریشه دایرکتوری داده وجود دارد.", + "Action \"%s\" not supported or implemented." : "عملی%s پشتیبانی یا اجرا نشده است.", + "Authentication failed, wrong token or provider ID given" : "تأیید اعتبار انجام نشد ، نشانه اشتباه یا شناسه ارائه دهنده داده شد", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "پارامترهای موجود برای تکمیل درخواست. پارامترهای موجود نیست%s", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "شناسه%1$s قبلاً توسط ارائه دهنده فدراسیون ابر استفاده شده است%2$s", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ارائه دهنده فدراسیون Cloud با شناسه:%s وجود ندارد.", + "Could not obtain lock type %d on \"%s\"." : "نمی توان نوع%d قفل را به دست آورد%s", + "Storage unauthorized. %s" : "ذخیره سازی غیر مجاز.%s", + "Storage incomplete configuration. %s" : "پیکربندی ناقص ذخیره سازی.%s
", + "Storage connection error. %s" : "خطای اتصال ذخیره سازی%s", + "Storage is temporarily not available" : "ذخیره سازی به طور موقت در دسترس نیست", + "Storage connection timeout. %s" : "مدت زمان اتصال ذخیره سازی%s", + "Following databases are supported: %s" : "پایگاه‌داده‌ های ذکر شده مورد نیاز است: %s", + "Following platforms are supported: %s" : "سیستم عامل های زیر پشتیبانی می شوند%s", + "Overview" : "بررسی اجمالی", + "Basic settings" : "تنظیمات پایه", + "Sharing" : "اشتراک گذاری", + "Security" : "امنیت", + "Groupware" : "گروه های نرم افزاری", + "Personal info" : "مشخصات شخصی", + "Mobile & desktop" : "موبایل و دسک تاپ", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "این امر معمولاً با دسترسی به وب سرور دسترسی به فهرست برنامه ها یا غیرفعال کردن برنامه در پرونده پیکربندی قابل رفع است. دیدن%s" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/fi.js b/docker/overlays/nextcloud/html/lib/l10n/fi.js new file mode 100644 index 0000000..68423d2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fi.js @@ -0,0 +1,197 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Hakemistoon \"config\" kirjoittaminen ei onnistu!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tämän voi yleensä korjata antamalla http-palvelimelle kirjoitusoikeuden asetushakemistoon", + "See %s" : "Katso %s", + "Sample configuration detected" : "Esimerkkimääritykset havaittu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "On havaittu, että esimerkkimäärityksen on kopioitu. Se voi rikkoa asennuksesi, eikä sitä tueta. Lue ohjeet ennen kuin muutat config.php tiedostoa.", + "%1$s and %2$s" : "%1$s ja %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ja %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ja %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ja %5$s", + "PHP %s or higher is required." : "PHP %s tai sitä uudempi vaaditaan.", + "PHP with a version lower than %s is required." : "PHP versiota %s alempi tarvitaan.", + "%sbit or higher PHP required." : "%s-bit tai korkeampi PHP vaaditaan.", + "The following databases are supported: %s" : "Seuraavat tietokannat ovat tuettuja: %s", + "The command line tool %s could not be found" : "Komentorivityökalua %s ei löytynyt", + "The library %s is not available." : "Kirjastoa %s ei ole käytettävissä.", + "The following platforms are supported: %s" : "Seuraavat alustat ovat tuettuja: %s", + "Server version %s or higher is required." : "Palvelinversio %s tai sitä uudempi vaaditaan.", + "Server version %s or lower is required." : "Palvelinversio %s tai alhaisempi vaaditaan.", + "Logged in user must be an admin" : "Sisäänkirjautuneen käyttäjän tulee olla ylläpitäjä", + "Wiping of device %s has started" : "Laitteen %s tyhjennys aloitettiin", + "Wiping of device »%s« has started" : "Laitteen »%s« tyhjennys aloitettiin", + "»%s« started remote wipe" : "»%s« aloitti etätyhjennyksen", + "Wiping of device %s has finished" : "Laitteen %s tyhjennys valmistui", + "Wiping of device »%s« has finished" : "Laitteen »%s« tyhjennys valmistui", + "Remote wipe started" : "Etätyhjennys aloitettiin", + "A remote wipe was started on device %s" : "Laitteen %s etätyhjennys aloitettiin", + "Remote wipe finished" : "Etätyhjennys valmistui", + "The remote wipe on %s has finished" : "Etätyhjennys laitteella %s valmistui", + "Authentication" : "Tunnistautuminen", + "Unknown filetype" : "Tuntematon tiedostotyyppi", + "Invalid image" : "Virheellinen kuva", + "Avatar image is not square" : "Avatar-kuva ei ole neliö", + "today" : "tänään", + "tomorrow" : "huomenna", + "yesterday" : "eilen", + "_in %n day_::_in %n days_" : ["%n päivän päästä","%n päivän päästä"], + "_%n day ago_::_%n days ago_" : ["%n päivä sitten","%n päivää sitten"], + "next month" : "ensi kuussa", + "last month" : "viime kuussa", + "_in %n month_::_in %n months_" : ["%n kuukauden päästä","%n kuukauden päästä"], + "_%n month ago_::_%n months ago_" : ["%n kuukausi sitten","%n kuukautta sitten"], + "next year" : "ensi vuonna", + "last year" : "viime vuonna", + "_in %n year_::_in %n years_" : ["%n vuoden päästä","%n vuoden päästä"], + "_%n year ago_::_%n years ago_" : ["%n vuosi sitten","%n vuotta sitten"], + "_in %n hour_::_in %n hours_" : ["%n tunnin päästä","%n tunnin päästä"], + "_%n hour ago_::_%n hours ago_" : ["%n tunti sitten","%n tuntia sitten"], + "_in %n minute_::_in %n minutes_" : ["%n minuutin päästä","%n minuutin päästä"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuutti sitten","%n minuuttia sitten"], + "in a few seconds" : "muutaman sekunnin päästä", + "seconds ago" : "sekunteja sitten", + "Empty file" : "Tyhjä tiedosto", + "File name is a reserved word" : "Tiedoston nimi on varattu sana", + "File name contains at least one invalid character" : "Tiedoston nimi sisältää ainakin yhden virheellisen merkin", + "File name is too long" : "Tiedoston nimi on liian pitkä", + "Dot files are not allowed" : "Pistetiedostot eivät ole sallittuja", + "Empty filename is not allowed" : "Tiedostonimi ei voi olla tyhjä", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Sovellusta \"%s\" ei voi asentaa, koska appinfo-tiedostoa ei voi loi lukea.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Sovellusta \"%s\" ei voi asentaa, koska se ei ole yhteensopiva tämän palvelinversion kanssa.", + "__language_name__" : "suomi", + "This is an automatically sent email, please do not reply." : "Tämä on automaattisesti lähetetty viesti. Älä vastaa tähän viestiin.", + "Help" : "Ohje", + "Apps" : "Sovellukset", + "Settings" : "Asetukset", + "Log out" : "Kirjaudu ulos", + "Users" : "Käyttäjät", + "Unknown user" : "Tuntematon käyttäjä", + "Additional settings" : "Lisäasetukset", + "%s enter the database username and name." : "%s anna tietokannan käyttäjätunnus ja nimi.", + "%s enter the database username." : "%s anna tietokannan käyttäjätunnus.", + "%s enter the database name." : "%s anna tietokannan nimi.", + "%s you may not use dots in the database name" : "%s et voi käyttää pisteitä tietokannan nimessä", + "MySQL username and/or password not valid" : "MySQL-käyttäjätunnus ja/tai -salasana on väärin", + "You need to enter details of an existing account." : "Anna olemassa olevan tilin tiedot.", + "Oracle connection could not be established" : "Oracle-yhteyttä ei voitu muodostaa", + "Oracle username and/or password not valid" : "Oraclen käyttäjätunnus ja/tai salasana on väärin", + "PostgreSQL username and/or password not valid" : "PostgreSQL:n käyttäjätunnus ja/tai salasana on väärin", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X ei ole tuettu, joten %s ei toimi kunnolla tällä alustalla. Käytä omalla vastuulla!", + "For the best results, please consider using a GNU/Linux server instead." : "Käytä parhaan lopputuloksen saamiseksi GNU/Linux-palvelinta.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Vaikuttaa siltä, että tämä %s-instanssi toimii 32-bittisessä PHP-ympäristössä ja open_basedir-asetus on määritetty php.ini-tiedostossa. Tämä johtaa ongelmiin yli 4 gigatavun tiedostojen kanssa, eikä siksi ole suositeltavaa.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Poista open_basedir-asetus php.ini-tiedostosta tai vaihda 64-bittiseen PHP:hen.", + "Set an admin username." : "Aseta ylläpitäjän käyttäjätunnus.", + "Set an admin password." : "Aseta ylläpitäjän salasana.", + "Can't create or write into the data directory %s" : "Ei voi luoda tai kirjoittaa data-hakemistoon %s", + "Invalid Federated Cloud ID" : "Virheellinen federoidun pilven tunniste", + "Sharing backend %s not found" : "Jakamisen taustaosaa %s ei löytynyt", + "Sharing backend for %s not found" : "Jakamisen taustaosaa kohteelle %s ei löytynyt", + "Open »%s«" : "Avaa »%s«", + "You are not allowed to share %s" : "Oikeutesi eivät riitä kohteen %s jakamiseen.", + "Expiration date is in the past" : "Vanhenemispäivä on menneisyydessä", + "%1$s shared »%2$s« with you" : "%1$s jakoi kohteen »%2$s« kanssasi", + "%1$s shared »%2$s« with you." : "%1$s jakoi kohteen »%2$s« kanssasi.", + "Click the button below to open it." : "Napsauta alla olevaa painiketta avataksesi sen.", + "The requested share does not exist anymore" : "Pyydettyä jakoa ei ole enää olemassa", + "Could not find category \"%s\"" : "Luokkaa \"%s\" ei löytynyt", + "Sunday" : "sunnuntai", + "Monday" : "maanantai", + "Tuesday" : "tiistai", + "Wednesday" : "keskiviikko", + "Thursday" : "torstai", + "Friday" : "perjantai", + "Saturday" : "lauantai", + "Sun." : "Su", + "Mon." : "Ma", + "Tue." : "Ti", + "Wed." : "Ke", + "Thu." : "To", + "Fri." : "Pe", + "Sat." : "La", + "Su" : "Su", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "Ke", + "Th" : "To", + "Fr" : "Pe", + "Sa" : "La", + "January" : "tammikuu", + "February" : "helmikuu", + "March" : "maaliskuu", + "April" : "huhtikuu", + "May" : "toukokuu", + "June" : "kesäkuu", + "July" : "heinäkuu", + "August" : "elokuu", + "September" : "syyskuu", + "October" : "lokakuu", + "November" : "marraskuu", + "December" : "joulukuu", + "Jan." : "Tammi", + "Feb." : "Helmi", + "Mar." : "Maalis", + "Apr." : "Huhti", + "May." : "Touko", + "Jun." : "Kesä", + "Jul." : "Heinä", + "Aug." : "Elo", + "Sep." : "Syys", + "Oct." : "Loka", + "Nov." : "Marras", + "Dec." : "Joulu", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\" ja \"_.@-'\"", + "A valid username must be provided" : "Anna kelvollinen käyttäjätunnus", + "Username contains whitespace at the beginning or at the end" : "Käyttäjätunnus sisältää tyhjätilaa joko alussa tai lopussa", + "Username must not consist of dots only" : "Käyttäjänimi ei voi koostua vain pisteistä", + "A valid password must be provided" : "Anna kelvollinen salasana", + "The username is already being used" : "Käyttäjätunnus on jo käytössä", + "Could not create user" : "Ei voitu luoda käyttäjää", + "User disabled" : "Käyttäjä poistettu käytöstä", + "Login canceled by app" : "Kirjautuminen peruttiin sovelluksen toimesta", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Sovellusta \"%1$s\" ei voi asentaa, koska seuraavat riippuvuudet eivät täyty: %2$s", + "a safe home for all your data" : "turvallinen koti kaikille tiedostoillesi", + "File is currently busy, please try again later" : "Tiedosto on parhaillaan käytössä, yritä myöhemmin uudelleen", + "Can't read file" : "Tiedostoa ei voi lukea", + "Application is not enabled" : "Sovellusta ei ole otettu käyttöön", + "Authentication error" : "Tunnistautumisvirhe", + "Token expired. Please reload page." : "Valtuutus vanheni. Lataa sivu uudelleen.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Tietokanta-ajureita (sqlite, mysql tai postgresql) ei ole asennettu.", + "Cannot write into \"config\" directory" : "Hakemistoon \"config\" kirjoittaminen ei onnistu", + "Cannot write into \"apps\" directory" : "Hakemistoon \"apps\" kirjoittaminen ei onnistu", + "Cannot create \"data\" directory" : "Hakemiston \"data\" luominen ei onnistu", + "Setting locale to %s failed" : "Maa-asetuksen %s asettaminen epäonnistui", + "Please install one of these locales on your system and restart your webserver." : "Asenna ainakin yksi kyseisistä maa-asetuksista järjestelmään ja käynnistä http-palvelin uudelleen.", + "PHP module %s not installed." : "PHP-moduulia %s ei ole asennettu.", + "Please ask your server administrator to install the module." : "Pyydä palvelimen ylläpitäjää asentamaan moduulin.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-asetusta \"%s\" ei ole asetettu arvoon \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload on asetettu arvoon \"%s\" odotetun arvon \"0\" sijaan", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Korjaa tämä ongelma asettamalla mbstring.func_overload arvoon 0 php.ini-tiedostossasi", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Vähintään libxml2 2.7.0 vaaditaan. %s on asennettu.", + "To fix this issue update your libxml2 version and restart your web server." : "Päivitä libxml2:n versio ja käynnistä http-palvelin uudelleen.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tämä johtuu todennäköisesti välimuistista tai kiihdyttimestä kuten Zend OPcachesta tai eAcceleratorista.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduulit on asennettu, mutta ovatko ne vieläkin listattu puuttuviksi?", + "Please ask your server administrator to restart the web server." : "Pyydä palvelimen ylläpitäjää käynnistämään web-palvelin uudelleen.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 vaaditaan", + "Please upgrade your database version" : "Päivitä tietokantasi versio", + "Your data directory is readable by other users" : "Data-hakemisto on muiden käyttäjien luettavissa", + "Your data directory must be an absolute path" : "Data-hakemiston tulee olla absoluuttinen polku", + "Check the value of \"datadirectory\" in your configuration" : "Tarkista \"datadirectory\"-arvo asetuksistasi", + "Your data directory is invalid" : "Datahakemistosi on virheellinen", + "Action \"%s\" not supported or implemented." : "Toiminto \"%s\" ei ole tuettu tai sitä ei ole toteutettu.", + "Could not obtain lock type %d on \"%s\"." : "Lukitustapaa %d ei saatu kohteelle \"%s\".", + "Storage unauthorized. %s" : "Tallennustila ei ole valtuutettu. %s", + "Storage incomplete configuration. %s" : "Tallennustilan puutteellinen määritys. %s", + "Storage connection error. %s" : "Tallennustilan yhteysvirhe. %s", + "Storage is temporarily not available" : "Tallennustila on tilapäisesti pois käytöstä", + "Storage connection timeout. %s" : "Tallennustilan yhteyden aikakatkaisu. %s", + "Following databases are supported: %s" : "Seuraavat tietokannat ovat tuettuja: %s", + "Following platforms are supported: %s" : "Seuraavat alustat ovat tuettuja: %s", + "Basic settings" : "Perusasetukset", + "Sharing" : "Jakaminen", + "Security" : "Turvallisuus", + "Personal info" : "Henkilökohtaiset tiedot", + "Mobile & desktop" : "Mobiili ja työpöytä" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/fi.json b/docker/overlays/nextcloud/html/lib/l10n/fi.json new file mode 100644 index 0000000..ccfed60 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fi.json @@ -0,0 +1,195 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Hakemistoon \"config\" kirjoittaminen ei onnistu!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tämän voi yleensä korjata antamalla http-palvelimelle kirjoitusoikeuden asetushakemistoon", + "See %s" : "Katso %s", + "Sample configuration detected" : "Esimerkkimääritykset havaittu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "On havaittu, että esimerkkimäärityksen on kopioitu. Se voi rikkoa asennuksesi, eikä sitä tueta. Lue ohjeet ennen kuin muutat config.php tiedostoa.", + "%1$s and %2$s" : "%1$s ja %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ja %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ja %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ja %5$s", + "PHP %s or higher is required." : "PHP %s tai sitä uudempi vaaditaan.", + "PHP with a version lower than %s is required." : "PHP versiota %s alempi tarvitaan.", + "%sbit or higher PHP required." : "%s-bit tai korkeampi PHP vaaditaan.", + "The following databases are supported: %s" : "Seuraavat tietokannat ovat tuettuja: %s", + "The command line tool %s could not be found" : "Komentorivityökalua %s ei löytynyt", + "The library %s is not available." : "Kirjastoa %s ei ole käytettävissä.", + "The following platforms are supported: %s" : "Seuraavat alustat ovat tuettuja: %s", + "Server version %s or higher is required." : "Palvelinversio %s tai sitä uudempi vaaditaan.", + "Server version %s or lower is required." : "Palvelinversio %s tai alhaisempi vaaditaan.", + "Logged in user must be an admin" : "Sisäänkirjautuneen käyttäjän tulee olla ylläpitäjä", + "Wiping of device %s has started" : "Laitteen %s tyhjennys aloitettiin", + "Wiping of device »%s« has started" : "Laitteen »%s« tyhjennys aloitettiin", + "»%s« started remote wipe" : "»%s« aloitti etätyhjennyksen", + "Wiping of device %s has finished" : "Laitteen %s tyhjennys valmistui", + "Wiping of device »%s« has finished" : "Laitteen »%s« tyhjennys valmistui", + "Remote wipe started" : "Etätyhjennys aloitettiin", + "A remote wipe was started on device %s" : "Laitteen %s etätyhjennys aloitettiin", + "Remote wipe finished" : "Etätyhjennys valmistui", + "The remote wipe on %s has finished" : "Etätyhjennys laitteella %s valmistui", + "Authentication" : "Tunnistautuminen", + "Unknown filetype" : "Tuntematon tiedostotyyppi", + "Invalid image" : "Virheellinen kuva", + "Avatar image is not square" : "Avatar-kuva ei ole neliö", + "today" : "tänään", + "tomorrow" : "huomenna", + "yesterday" : "eilen", + "_in %n day_::_in %n days_" : ["%n päivän päästä","%n päivän päästä"], + "_%n day ago_::_%n days ago_" : ["%n päivä sitten","%n päivää sitten"], + "next month" : "ensi kuussa", + "last month" : "viime kuussa", + "_in %n month_::_in %n months_" : ["%n kuukauden päästä","%n kuukauden päästä"], + "_%n month ago_::_%n months ago_" : ["%n kuukausi sitten","%n kuukautta sitten"], + "next year" : "ensi vuonna", + "last year" : "viime vuonna", + "_in %n year_::_in %n years_" : ["%n vuoden päästä","%n vuoden päästä"], + "_%n year ago_::_%n years ago_" : ["%n vuosi sitten","%n vuotta sitten"], + "_in %n hour_::_in %n hours_" : ["%n tunnin päästä","%n tunnin päästä"], + "_%n hour ago_::_%n hours ago_" : ["%n tunti sitten","%n tuntia sitten"], + "_in %n minute_::_in %n minutes_" : ["%n minuutin päästä","%n minuutin päästä"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuutti sitten","%n minuuttia sitten"], + "in a few seconds" : "muutaman sekunnin päästä", + "seconds ago" : "sekunteja sitten", + "Empty file" : "Tyhjä tiedosto", + "File name is a reserved word" : "Tiedoston nimi on varattu sana", + "File name contains at least one invalid character" : "Tiedoston nimi sisältää ainakin yhden virheellisen merkin", + "File name is too long" : "Tiedoston nimi on liian pitkä", + "Dot files are not allowed" : "Pistetiedostot eivät ole sallittuja", + "Empty filename is not allowed" : "Tiedostonimi ei voi olla tyhjä", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Sovellusta \"%s\" ei voi asentaa, koska appinfo-tiedostoa ei voi loi lukea.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Sovellusta \"%s\" ei voi asentaa, koska se ei ole yhteensopiva tämän palvelinversion kanssa.", + "__language_name__" : "suomi", + "This is an automatically sent email, please do not reply." : "Tämä on automaattisesti lähetetty viesti. Älä vastaa tähän viestiin.", + "Help" : "Ohje", + "Apps" : "Sovellukset", + "Settings" : "Asetukset", + "Log out" : "Kirjaudu ulos", + "Users" : "Käyttäjät", + "Unknown user" : "Tuntematon käyttäjä", + "Additional settings" : "Lisäasetukset", + "%s enter the database username and name." : "%s anna tietokannan käyttäjätunnus ja nimi.", + "%s enter the database username." : "%s anna tietokannan käyttäjätunnus.", + "%s enter the database name." : "%s anna tietokannan nimi.", + "%s you may not use dots in the database name" : "%s et voi käyttää pisteitä tietokannan nimessä", + "MySQL username and/or password not valid" : "MySQL-käyttäjätunnus ja/tai -salasana on väärin", + "You need to enter details of an existing account." : "Anna olemassa olevan tilin tiedot.", + "Oracle connection could not be established" : "Oracle-yhteyttä ei voitu muodostaa", + "Oracle username and/or password not valid" : "Oraclen käyttäjätunnus ja/tai salasana on väärin", + "PostgreSQL username and/or password not valid" : "PostgreSQL:n käyttäjätunnus ja/tai salasana on väärin", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X ei ole tuettu, joten %s ei toimi kunnolla tällä alustalla. Käytä omalla vastuulla!", + "For the best results, please consider using a GNU/Linux server instead." : "Käytä parhaan lopputuloksen saamiseksi GNU/Linux-palvelinta.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Vaikuttaa siltä, että tämä %s-instanssi toimii 32-bittisessä PHP-ympäristössä ja open_basedir-asetus on määritetty php.ini-tiedostossa. Tämä johtaa ongelmiin yli 4 gigatavun tiedostojen kanssa, eikä siksi ole suositeltavaa.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Poista open_basedir-asetus php.ini-tiedostosta tai vaihda 64-bittiseen PHP:hen.", + "Set an admin username." : "Aseta ylläpitäjän käyttäjätunnus.", + "Set an admin password." : "Aseta ylläpitäjän salasana.", + "Can't create or write into the data directory %s" : "Ei voi luoda tai kirjoittaa data-hakemistoon %s", + "Invalid Federated Cloud ID" : "Virheellinen federoidun pilven tunniste", + "Sharing backend %s not found" : "Jakamisen taustaosaa %s ei löytynyt", + "Sharing backend for %s not found" : "Jakamisen taustaosaa kohteelle %s ei löytynyt", + "Open »%s«" : "Avaa »%s«", + "You are not allowed to share %s" : "Oikeutesi eivät riitä kohteen %s jakamiseen.", + "Expiration date is in the past" : "Vanhenemispäivä on menneisyydessä", + "%1$s shared »%2$s« with you" : "%1$s jakoi kohteen »%2$s« kanssasi", + "%1$s shared »%2$s« with you." : "%1$s jakoi kohteen »%2$s« kanssasi.", + "Click the button below to open it." : "Napsauta alla olevaa painiketta avataksesi sen.", + "The requested share does not exist anymore" : "Pyydettyä jakoa ei ole enää olemassa", + "Could not find category \"%s\"" : "Luokkaa \"%s\" ei löytynyt", + "Sunday" : "sunnuntai", + "Monday" : "maanantai", + "Tuesday" : "tiistai", + "Wednesday" : "keskiviikko", + "Thursday" : "torstai", + "Friday" : "perjantai", + "Saturday" : "lauantai", + "Sun." : "Su", + "Mon." : "Ma", + "Tue." : "Ti", + "Wed." : "Ke", + "Thu." : "To", + "Fri." : "Pe", + "Sat." : "La", + "Su" : "Su", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "Ke", + "Th" : "To", + "Fr" : "Pe", + "Sa" : "La", + "January" : "tammikuu", + "February" : "helmikuu", + "March" : "maaliskuu", + "April" : "huhtikuu", + "May" : "toukokuu", + "June" : "kesäkuu", + "July" : "heinäkuu", + "August" : "elokuu", + "September" : "syyskuu", + "October" : "lokakuu", + "November" : "marraskuu", + "December" : "joulukuu", + "Jan." : "Tammi", + "Feb." : "Helmi", + "Mar." : "Maalis", + "Apr." : "Huhti", + "May." : "Touko", + "Jun." : "Kesä", + "Jul." : "Heinä", + "Aug." : "Elo", + "Sep." : "Syys", + "Oct." : "Loka", + "Nov." : "Marras", + "Dec." : "Joulu", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\" ja \"_.@-'\"", + "A valid username must be provided" : "Anna kelvollinen käyttäjätunnus", + "Username contains whitespace at the beginning or at the end" : "Käyttäjätunnus sisältää tyhjätilaa joko alussa tai lopussa", + "Username must not consist of dots only" : "Käyttäjänimi ei voi koostua vain pisteistä", + "A valid password must be provided" : "Anna kelvollinen salasana", + "The username is already being used" : "Käyttäjätunnus on jo käytössä", + "Could not create user" : "Ei voitu luoda käyttäjää", + "User disabled" : "Käyttäjä poistettu käytöstä", + "Login canceled by app" : "Kirjautuminen peruttiin sovelluksen toimesta", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Sovellusta \"%1$s\" ei voi asentaa, koska seuraavat riippuvuudet eivät täyty: %2$s", + "a safe home for all your data" : "turvallinen koti kaikille tiedostoillesi", + "File is currently busy, please try again later" : "Tiedosto on parhaillaan käytössä, yritä myöhemmin uudelleen", + "Can't read file" : "Tiedostoa ei voi lukea", + "Application is not enabled" : "Sovellusta ei ole otettu käyttöön", + "Authentication error" : "Tunnistautumisvirhe", + "Token expired. Please reload page." : "Valtuutus vanheni. Lataa sivu uudelleen.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Tietokanta-ajureita (sqlite, mysql tai postgresql) ei ole asennettu.", + "Cannot write into \"config\" directory" : "Hakemistoon \"config\" kirjoittaminen ei onnistu", + "Cannot write into \"apps\" directory" : "Hakemistoon \"apps\" kirjoittaminen ei onnistu", + "Cannot create \"data\" directory" : "Hakemiston \"data\" luominen ei onnistu", + "Setting locale to %s failed" : "Maa-asetuksen %s asettaminen epäonnistui", + "Please install one of these locales on your system and restart your webserver." : "Asenna ainakin yksi kyseisistä maa-asetuksista järjestelmään ja käynnistä http-palvelin uudelleen.", + "PHP module %s not installed." : "PHP-moduulia %s ei ole asennettu.", + "Please ask your server administrator to install the module." : "Pyydä palvelimen ylläpitäjää asentamaan moduulin.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-asetusta \"%s\" ei ole asetettu arvoon \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload on asetettu arvoon \"%s\" odotetun arvon \"0\" sijaan", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Korjaa tämä ongelma asettamalla mbstring.func_overload arvoon 0 php.ini-tiedostossasi", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Vähintään libxml2 2.7.0 vaaditaan. %s on asennettu.", + "To fix this issue update your libxml2 version and restart your web server." : "Päivitä libxml2:n versio ja käynnistä http-palvelin uudelleen.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tämä johtuu todennäköisesti välimuistista tai kiihdyttimestä kuten Zend OPcachesta tai eAcceleratorista.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduulit on asennettu, mutta ovatko ne vieläkin listattu puuttuviksi?", + "Please ask your server administrator to restart the web server." : "Pyydä palvelimen ylläpitäjää käynnistämään web-palvelin uudelleen.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 vaaditaan", + "Please upgrade your database version" : "Päivitä tietokantasi versio", + "Your data directory is readable by other users" : "Data-hakemisto on muiden käyttäjien luettavissa", + "Your data directory must be an absolute path" : "Data-hakemiston tulee olla absoluuttinen polku", + "Check the value of \"datadirectory\" in your configuration" : "Tarkista \"datadirectory\"-arvo asetuksistasi", + "Your data directory is invalid" : "Datahakemistosi on virheellinen", + "Action \"%s\" not supported or implemented." : "Toiminto \"%s\" ei ole tuettu tai sitä ei ole toteutettu.", + "Could not obtain lock type %d on \"%s\"." : "Lukitustapaa %d ei saatu kohteelle \"%s\".", + "Storage unauthorized. %s" : "Tallennustila ei ole valtuutettu. %s", + "Storage incomplete configuration. %s" : "Tallennustilan puutteellinen määritys. %s", + "Storage connection error. %s" : "Tallennustilan yhteysvirhe. %s", + "Storage is temporarily not available" : "Tallennustila on tilapäisesti pois käytöstä", + "Storage connection timeout. %s" : "Tallennustilan yhteyden aikakatkaisu. %s", + "Following databases are supported: %s" : "Seuraavat tietokannat ovat tuettuja: %s", + "Following platforms are supported: %s" : "Seuraavat alustat ovat tuettuja: %s", + "Basic settings" : "Perusasetukset", + "Sharing" : "Jakaminen", + "Security" : "Turvallisuus", + "Personal info" : "Henkilökohtaiset tiedot", + "Mobile & desktop" : "Mobiili ja työpöytä" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/fo.js b/docker/overlays/nextcloud/html/lib/l10n/fo.js new file mode 100644 index 0000000..9705dec --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fo.js @@ -0,0 +1,7 @@ +OC.L10N.register( + "lib", + { + "Authentication error" : "Samgildis feilur", + "Security" : "Trygd" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/fo.json b/docker/overlays/nextcloud/html/lib/l10n/fo.json new file mode 100644 index 0000000..0d2d989 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fo.json @@ -0,0 +1,5 @@ +{ "translations": { + "Authentication error" : "Samgildis feilur", + "Security" : "Trygd" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/fr.js b/docker/overlays/nextcloud/html/lib/l10n/fr.js new file mode 100644 index 0000000..d3963fa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fr.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Impossible d’écrire dans le répertoire « config » !", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\"", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true.", + "See %s" : "Voir %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire de configuration.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Les fichiers de l'application %1$s n'ont pas été remplacés correctement. Veuillez vérifier que c'est une version compatible avec le serveur.", + "Sample configuration detected" : "Configuration d'exemple détectée", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Il a été détecté que la configuration donnée à titre d'exemple a été copiée. Cela peut rendre votre installation inopérante et n'est pas pris en charge. Veuillez lire la documentation avant d'effectuer des modifications dans config.php", + "Other activities" : "Autres activités", + "%1$s and %2$s" : "%1$s et %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s et %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s et %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s et %5$s", + "Education Edition" : "Édition pour l'éducation ", + "Enterprise bundle" : "Pack pour entreprise", + "Groupware bundle" : "Pack pour travail collaboratif", + "Hub bundle" : "Noyau de paquets", + "Social sharing bundle" : "Pack pour partage social", + "PHP %s or higher is required." : "PHP %s ou supérieur est requis.", + "PHP with a version lower than %s is required." : "PHP avec une version antérieure à %s est requis.", + "%sbit or higher PHP required." : "PHP %sbits ou supérieur est requis.", + "The following architectures are supported: %s" : "Les architectures suivantes sont prises en charge : %s", + "The following databases are supported: %s" : "Les bases de données suivantes sont prises en charge : %s", + "The command line tool %s could not be found" : "La commande %s est introuvable", + "The library %s is not available." : "La librairie %s n'est pas disponible.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "La librairie %1$s doit être au moins à la version %2$s. Version disponible : %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "La librairie %1$s doit avoir une version antérieure à %2$s. Version disponible : %3$s.", + "The following platforms are supported: %s" : "Les plateformes suivantes sont prises en charge : %s", + "Server version %s or higher is required." : "Un serveur de version %s ou supérieure est requis.", + "Server version %s or lower is required." : "Un serveur de version %s ou inférieure est requis.", + "Logged in user must be an admin or sub admin" : "L'utilisateur connecté doit être administrateur ou sous-administrateur", + "Logged in user must be an admin" : "L'utilisateur connecté doit être un administrateur", + "Wiping of device %s has started" : "L'effaçage de l'appareil %s à démarré", + "Wiping of device »%s« has started" : "L'effaçage de l'appareil \"%s\" à démarré", + "»%s« started remote wipe" : "\"%s\" à démarré l'effaçage distant", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "L'appareil ou l'application \"%s\" à démarré le processus d'effaçage distant. Vous recevrez un autre e-mail une fois le processus terminé", + "Wiping of device %s has finished" : "L'effaçage de l'appareil %s est terminé", + "Wiping of device »%s« has finished" : "L'effaçage de l'appareil \"%s\" est terminé", + "»%s« finished remote wipe" : "\"%s\" à terminé l'effaçage distant", + "Device or application »%s« has finished the remote wipe process." : "L'appareil ou l'application \"%s\" à terminé le processus d'effaçage distant.", + "Remote wipe started" : "Nettoyage à distance lancé", + "A remote wipe was started on device %s" : "Un nettoyage à distance à été lancé sur l'appareil %s", + "Remote wipe finished" : "Nettoyage à distance terminé", + "The remote wipe on %s has finished" : "Le nettoyage à distance de %s est terminé", + "Authentication" : "Authentification", + "Unknown filetype" : "Type de fichier inconnu", + "Invalid image" : "Image non valable", + "Avatar image is not square" : "L'image d'avatar n'est pas carré", + "today" : "aujourd'hui", + "tomorrow" : "demain", + "yesterday" : "hier", + "_in %n day_::_in %n days_" : ["dans %n jour","dans %n jours"], + "_%n day ago_::_%n days ago_" : ["il y a %n jour","il y a %n jours"], + "next month" : "mois suivant", + "last month" : "le mois dernier", + "_in %n month_::_in %n months_" : ["dans %n mois","dans %n mois"], + "_%n month ago_::_%n months ago_" : ["Il y a %n mois","Il y a %n mois"], + "next year" : "année suivante", + "last year" : "l'année dernière", + "_in %n year_::_in %n years_" : ["dans %n an","dans %n ans"], + "_%n year ago_::_%n years ago_" : ["il y a %n an","il y a %n ans"], + "_in %n hour_::_in %n hours_" : ["dans %n heure","dans %n heures"], + "_%n hour ago_::_%n hours ago_" : ["Il y a %n heure","Il y a %n heures"], + "_in %n minute_::_in %n minutes_" : ["dans %n minute","dans %n minutes"], + "_%n minute ago_::_%n minutes ago_" : ["il y a %n minute","il y a %n minutes"], + "in a few seconds" : "dans quelques secondes", + "seconds ago" : "il y a quelques secondes", + "Empty file" : "Fichier vide", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Le module avec l'ID: %s n'existe pas. Merci de l'activer dans les paramètres d'applications ou de contacter votre administrateur.", + "File name is a reserved word" : "Ce nom de fichier est un mot réservé", + "File name contains at least one invalid character" : "Le nom de fichier contient un (des) caractère(s) non valide(s)", + "File name is too long" : "Nom de fichier trop long", + "Dot files are not allowed" : "Le nom de fichier ne peut pas commencer par un point", + "Empty filename is not allowed" : "Le nom de fichier ne peut pas être vide", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application \"%s\" ne peut pas être installée car le fichier appinfo ne peut pas être lu.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application \"%s\" ne peut être installée car elle n'est pas compatible avec cette version du serveur", + "__language_name__" : "Français", + "This is an automatically sent email, please do not reply." : "Ceci est un e-mail envoyé automatiquement, veuillez ne pas y répondre.", + "Help" : "Aide", + "Apps" : "Applications", + "Settings" : "Paramètres", + "Log out" : "Se déconnecter", + "Users" : "Utilisateurs", + "Unknown user" : "Utilisateur inconnu", + "Additional settings" : "Paramètres supplémentaires", + "%s enter the database username and name." : "%s entrez le nom d'utilisateur et le nom de la base de données.", + "%s enter the database username." : "%s entrez le nom d'utilisateur de la base de données.", + "%s enter the database name." : "%s entrez le nom de la base de données.", + "%s you may not use dots in the database name" : "%s vous ne pouvez pas utiliser de points dans le nom de la base de données", + "MySQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base MySQL non valide(s)", + "You need to enter details of an existing account." : "Vous devez indiquer les détails d'un compte existant.", + "Oracle connection could not be established" : "La connexion Oracle ne peut être établie", + "Oracle username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base Oracle non valide(s)", + "PostgreSQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base PostgreSQL non valide(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X n'est pas pris en charge et %s ne fonctionnera pas correctement sur cette plate-forme. Son utilisation est à vos risques et périls !", + "For the best results, please consider using a GNU/Linux server instead." : "Pour obtenir les meilleurs résultats, vous devriez utiliser un serveur GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32-bit et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez supprimer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.", + "Set an admin username." : "Spécifiez un nom d'utilisateur pour l'administrateur.", + "Set an admin password." : "Spécifiez un mot de passe pour l'administrateur.", + "Can't create or write into the data directory %s" : "Impossible de créer, ou d'écrire dans, le répertoire des données %s", + "Invalid Federated Cloud ID" : "ID Federated Cloud incorrect", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Le service de partage %s doit implémenter l'interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Service de partage %s non trouvé", + "Sharing backend for %s not found" : "Le service de partage pour %s est introuvable", + "%1$s shared »%2$s« with you and wants to add:" : "%1$sa partagé \"%2$s\" avec vous et veut ajouter :", + "%1$s shared »%2$s« with you and wants to add" : "%1$sa partagé \"%2$s\" avec vous et veut ajouter", + "»%s« added a note to a file shared with you" : "\"%s\" a ajouté une note à un fichier partagé avec vous", + "Open »%s«" : "Ouvrir «%s»", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Vous n'êtes pas autorisé à partager %s", + "Can’t increase permissions of %s" : "Impossible d'augmenter les permissions de %s", + "Files can’t be shared with delete permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de suppression", + "Files can’t be shared with create permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de création", + "Expiration date is in the past" : "La date d'expiration est dans le passé", + "Can’t set expiration date more than %s days in the future" : "Impossible de définir la date d'expiration à plus de %s jours dans le futur", + "%1$s shared »%2$s« with you" : "%1$s a partagé «%2$s» avec vous", + "%1$s shared »%2$s« with you." : "%1$s a partagé «%2$s» avec vous.", + "Click the button below to open it." : "Cliquez sur le bouton ci-dessous pour l'ouvrir", + "The requested share does not exist anymore" : "Le partage demandé n'existe plus", + "Could not find category \"%s\"" : "Impossible de trouver la catégorie \"%s\"", + "Sunday" : "Dimanche", + "Monday" : "Lundi", + "Tuesday" : "Mardi", + "Wednesday" : "Mercredi", + "Thursday" : "Jeudi", + "Friday" : "Vendredi", + "Saturday" : "Samedi", + "Sun." : "Dim.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Jeu.", + "Fri." : "Ven.", + "Sat." : "Sam.", + "Su" : "Di", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Je", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Janvier", + "February" : "Février", + "March" : "Mars", + "April" : "Avril", + "May" : "Mai", + "June" : "Juin", + "July" : "Juillet", + "August" : "Août", + "September" : "Septembre", + "October" : "Octobre", + "November" : "Novembre", + "December" : "Décembre", + "Jan." : "Jan.", + "Feb." : "Fév.", + "Mar." : "Mars", + "Apr." : "Avr.", + "May." : "Mai", + "Jun." : "Juin", + "Jul." : "Juil.", + "Aug." : "Août", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Déc.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Seuls les caractères suivants sont autorisés dans un nom d'utilisateur : \"a-z\", \"A-Z\", \"0-9\", \"_@-\" et \".\" (le point)", + "A valid username must be provided" : "Un nom d'utilisateur valide doit être saisi", + "Username contains whitespace at the beginning or at the end" : "Le nom d'utilisateur contient des espaces au début ou à la fin", + "Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points", + "Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur", + "A valid password must be provided" : "Un mot de passe valide doit être saisi", + "The username is already being used" : "Ce nom d'utilisateur est déjà utilisé", + "Could not create user" : "Impossible de créer l'utilisateur", + "User disabled" : "Utilisateur désactivé", + "Login canceled by app" : "L'authentification a été annulé par l'application", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", + "a safe home for all your data" : "un lieu sûr pour toutes vos données", + "File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard", + "Can't read file" : "Impossible de lire le fichier", + "Application is not enabled" : "L'application n'est pas activée", + "Authentication error" : "Erreur d'authentification", + "Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).", + "Cannot write into \"config\" directory" : "Impossible d’écrire dans le répertoire \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s", + "Cannot write into \"apps\" directory" : "Impossible d’écrire dans le répertoire \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant l'appstore dans le fichier de configuration.", + "Cannot create \"data\" directory" : "Impossible de créer le dossier \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Le problème de permissions peut généralement être résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s.", + "Setting locale to %s failed" : "Echec de la spécification des paramètres régionaux à %s", + "Please install one of these locales on your system and restart your webserver." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.", + "PHP module %s not installed." : "Le module PHP %s n’est pas installé.", + "Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.", + "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload est à \"%s\" alors que la valeur \"0\" est attendue", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pour corriger ce problème mettez mbstring.func_overload à 0 dans votre php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 au moins est requis. Actuellement %s est installé.", + "To fix this issue update your libxml2 version and restart your web server." : "Pour régler ce problème, mettez à jour votre version de libxml2 et redémarrez votre serveur web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP semble configuré de manière à supprimer les blocs PHPdoc du code. Cela rendra plusieurs applications de base inaccessibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "La raison est probablement l'utilisation d'un cache / accélérateur tel que Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Les modules PHP ont été installés mais sont toujours indiqués comme manquants ?", + "Please ask your server administrator to restart the web server." : "Veuillez demander à votre administrateur serveur de redémarrer le serveur web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requis", + "Please upgrade your database version" : "Veuillez mettre à jour votre gestionnaire de base de données", + "Your data directory is readable by other users" : "Votre répertoire est lisible par les autres utilisateurs", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu ne puisse pas être listé par les autres utilisateurs.", + "Your data directory must be an absolute path" : "Le chemin de votre répertoire doit être un lien absolu", + "Check the value of \"datadirectory\" in your configuration" : "Verifiez la valeur de \"datadirectory\" dans votre configuration", + "Your data directory is invalid" : "Votre répertoire n'est pas valide", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assurez-vous que le répertoire de données contient un fichier \".ocdata\" à sa racine.", + "Action \"%s\" not supported or implemented." : "Action \"%s\" non supportée ou implémentée.", + "Authentication failed, wrong token or provider ID given" : "Échec de l'authentification, jeton erroné ou identification du fournisseur donnée", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Paramètres manquants pour compléter la requête. Paramètres manquants : \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'identifiant \"%1$s\" est déjà utilisé par le fournisseur de cloud \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Le fournisseur de cloud avec l'identifiant \"%s\" n'existe pas.", + "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur \"%s\".", + "Storage unauthorized. %s" : "Espace de stockage non autorisé. %s", + "Storage incomplete configuration. %s" : "Configuration de l'espace de stockage incomplète. %s", + "Storage connection error. %s" : "Erreur de connexion à l'espace stockage. %s", + "Storage is temporarily not available" : "Le support de stockage est temporairement indisponible", + "Storage connection timeout. %s" : "Le délai d'attente pour la connexion à l'espace de stockage a été dépassé. %s", + "Following databases are supported: %s" : "Les bases de données suivantes sont supportées : %s", + "Following platforms are supported: %s" : "Les plateformes suivantes sont prises en charge : %s", + "Overview" : "Vue d'ensemble", + "Basic settings" : "Paramètres de base", + "Sharing" : "Partage", + "Security" : "Sécurité", + "Groupware" : "Travail collaboratif", + "Personal info" : "Informations personnelles", + "Mobile & desktop" : "Mobile & bureau", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"apps\" ou en désactivant l'appstore dans le fichier de configuration. Voir %s" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/fr.json b/docker/overlays/nextcloud/html/lib/l10n/fr.json new file mode 100644 index 0000000..5746372 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/fr.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Impossible d’écrire dans le répertoire « config » !", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\"", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true.", + "See %s" : "Voir %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire de configuration.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Les fichiers de l'application %1$s n'ont pas été remplacés correctement. Veuillez vérifier que c'est une version compatible avec le serveur.", + "Sample configuration detected" : "Configuration d'exemple détectée", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Il a été détecté que la configuration donnée à titre d'exemple a été copiée. Cela peut rendre votre installation inopérante et n'est pas pris en charge. Veuillez lire la documentation avant d'effectuer des modifications dans config.php", + "Other activities" : "Autres activités", + "%1$s and %2$s" : "%1$s et %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s et %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s et %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s et %5$s", + "Education Edition" : "Édition pour l'éducation ", + "Enterprise bundle" : "Pack pour entreprise", + "Groupware bundle" : "Pack pour travail collaboratif", + "Hub bundle" : "Noyau de paquets", + "Social sharing bundle" : "Pack pour partage social", + "PHP %s or higher is required." : "PHP %s ou supérieur est requis.", + "PHP with a version lower than %s is required." : "PHP avec une version antérieure à %s est requis.", + "%sbit or higher PHP required." : "PHP %sbits ou supérieur est requis.", + "The following architectures are supported: %s" : "Les architectures suivantes sont prises en charge : %s", + "The following databases are supported: %s" : "Les bases de données suivantes sont prises en charge : %s", + "The command line tool %s could not be found" : "La commande %s est introuvable", + "The library %s is not available." : "La librairie %s n'est pas disponible.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "La librairie %1$s doit être au moins à la version %2$s. Version disponible : %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "La librairie %1$s doit avoir une version antérieure à %2$s. Version disponible : %3$s.", + "The following platforms are supported: %s" : "Les plateformes suivantes sont prises en charge : %s", + "Server version %s or higher is required." : "Un serveur de version %s ou supérieure est requis.", + "Server version %s or lower is required." : "Un serveur de version %s ou inférieure est requis.", + "Logged in user must be an admin or sub admin" : "L'utilisateur connecté doit être administrateur ou sous-administrateur", + "Logged in user must be an admin" : "L'utilisateur connecté doit être un administrateur", + "Wiping of device %s has started" : "L'effaçage de l'appareil %s à démarré", + "Wiping of device »%s« has started" : "L'effaçage de l'appareil \"%s\" à démarré", + "»%s« started remote wipe" : "\"%s\" à démarré l'effaçage distant", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "L'appareil ou l'application \"%s\" à démarré le processus d'effaçage distant. Vous recevrez un autre e-mail une fois le processus terminé", + "Wiping of device %s has finished" : "L'effaçage de l'appareil %s est terminé", + "Wiping of device »%s« has finished" : "L'effaçage de l'appareil \"%s\" est terminé", + "»%s« finished remote wipe" : "\"%s\" à terminé l'effaçage distant", + "Device or application »%s« has finished the remote wipe process." : "L'appareil ou l'application \"%s\" à terminé le processus d'effaçage distant.", + "Remote wipe started" : "Nettoyage à distance lancé", + "A remote wipe was started on device %s" : "Un nettoyage à distance à été lancé sur l'appareil %s", + "Remote wipe finished" : "Nettoyage à distance terminé", + "The remote wipe on %s has finished" : "Le nettoyage à distance de %s est terminé", + "Authentication" : "Authentification", + "Unknown filetype" : "Type de fichier inconnu", + "Invalid image" : "Image non valable", + "Avatar image is not square" : "L'image d'avatar n'est pas carré", + "today" : "aujourd'hui", + "tomorrow" : "demain", + "yesterday" : "hier", + "_in %n day_::_in %n days_" : ["dans %n jour","dans %n jours"], + "_%n day ago_::_%n days ago_" : ["il y a %n jour","il y a %n jours"], + "next month" : "mois suivant", + "last month" : "le mois dernier", + "_in %n month_::_in %n months_" : ["dans %n mois","dans %n mois"], + "_%n month ago_::_%n months ago_" : ["Il y a %n mois","Il y a %n mois"], + "next year" : "année suivante", + "last year" : "l'année dernière", + "_in %n year_::_in %n years_" : ["dans %n an","dans %n ans"], + "_%n year ago_::_%n years ago_" : ["il y a %n an","il y a %n ans"], + "_in %n hour_::_in %n hours_" : ["dans %n heure","dans %n heures"], + "_%n hour ago_::_%n hours ago_" : ["Il y a %n heure","Il y a %n heures"], + "_in %n minute_::_in %n minutes_" : ["dans %n minute","dans %n minutes"], + "_%n minute ago_::_%n minutes ago_" : ["il y a %n minute","il y a %n minutes"], + "in a few seconds" : "dans quelques secondes", + "seconds ago" : "il y a quelques secondes", + "Empty file" : "Fichier vide", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Le module avec l'ID: %s n'existe pas. Merci de l'activer dans les paramètres d'applications ou de contacter votre administrateur.", + "File name is a reserved word" : "Ce nom de fichier est un mot réservé", + "File name contains at least one invalid character" : "Le nom de fichier contient un (des) caractère(s) non valide(s)", + "File name is too long" : "Nom de fichier trop long", + "Dot files are not allowed" : "Le nom de fichier ne peut pas commencer par un point", + "Empty filename is not allowed" : "Le nom de fichier ne peut pas être vide", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application \"%s\" ne peut pas être installée car le fichier appinfo ne peut pas être lu.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application \"%s\" ne peut être installée car elle n'est pas compatible avec cette version du serveur", + "__language_name__" : "Français", + "This is an automatically sent email, please do not reply." : "Ceci est un e-mail envoyé automatiquement, veuillez ne pas y répondre.", + "Help" : "Aide", + "Apps" : "Applications", + "Settings" : "Paramètres", + "Log out" : "Se déconnecter", + "Users" : "Utilisateurs", + "Unknown user" : "Utilisateur inconnu", + "Additional settings" : "Paramètres supplémentaires", + "%s enter the database username and name." : "%s entrez le nom d'utilisateur et le nom de la base de données.", + "%s enter the database username." : "%s entrez le nom d'utilisateur de la base de données.", + "%s enter the database name." : "%s entrez le nom de la base de données.", + "%s you may not use dots in the database name" : "%s vous ne pouvez pas utiliser de points dans le nom de la base de données", + "MySQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base MySQL non valide(s)", + "You need to enter details of an existing account." : "Vous devez indiquer les détails d'un compte existant.", + "Oracle connection could not be established" : "La connexion Oracle ne peut être établie", + "Oracle username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base Oracle non valide(s)", + "PostgreSQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base PostgreSQL non valide(s)", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X n'est pas pris en charge et %s ne fonctionnera pas correctement sur cette plate-forme. Son utilisation est à vos risques et périls !", + "For the best results, please consider using a GNU/Linux server instead." : "Pour obtenir les meilleurs résultats, vous devriez utiliser un serveur GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32-bit et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez supprimer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.", + "Set an admin username." : "Spécifiez un nom d'utilisateur pour l'administrateur.", + "Set an admin password." : "Spécifiez un mot de passe pour l'administrateur.", + "Can't create or write into the data directory %s" : "Impossible de créer, ou d'écrire dans, le répertoire des données %s", + "Invalid Federated Cloud ID" : "ID Federated Cloud incorrect", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Le service de partage %s doit implémenter l'interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Service de partage %s non trouvé", + "Sharing backend for %s not found" : "Le service de partage pour %s est introuvable", + "%1$s shared »%2$s« with you and wants to add:" : "%1$sa partagé \"%2$s\" avec vous et veut ajouter :", + "%1$s shared »%2$s« with you and wants to add" : "%1$sa partagé \"%2$s\" avec vous et veut ajouter", + "»%s« added a note to a file shared with you" : "\"%s\" a ajouté une note à un fichier partagé avec vous", + "Open »%s«" : "Ouvrir «%s»", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Vous n'êtes pas autorisé à partager %s", + "Can’t increase permissions of %s" : "Impossible d'augmenter les permissions de %s", + "Files can’t be shared with delete permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de suppression", + "Files can’t be shared with create permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de création", + "Expiration date is in the past" : "La date d'expiration est dans le passé", + "Can’t set expiration date more than %s days in the future" : "Impossible de définir la date d'expiration à plus de %s jours dans le futur", + "%1$s shared »%2$s« with you" : "%1$s a partagé «%2$s» avec vous", + "%1$s shared »%2$s« with you." : "%1$s a partagé «%2$s» avec vous.", + "Click the button below to open it." : "Cliquez sur le bouton ci-dessous pour l'ouvrir", + "The requested share does not exist anymore" : "Le partage demandé n'existe plus", + "Could not find category \"%s\"" : "Impossible de trouver la catégorie \"%s\"", + "Sunday" : "Dimanche", + "Monday" : "Lundi", + "Tuesday" : "Mardi", + "Wednesday" : "Mercredi", + "Thursday" : "Jeudi", + "Friday" : "Vendredi", + "Saturday" : "Samedi", + "Sun." : "Dim.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Jeu.", + "Fri." : "Ven.", + "Sat." : "Sam.", + "Su" : "Di", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Je", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Janvier", + "February" : "Février", + "March" : "Mars", + "April" : "Avril", + "May" : "Mai", + "June" : "Juin", + "July" : "Juillet", + "August" : "Août", + "September" : "Septembre", + "October" : "Octobre", + "November" : "Novembre", + "December" : "Décembre", + "Jan." : "Jan.", + "Feb." : "Fév.", + "Mar." : "Mars", + "Apr." : "Avr.", + "May." : "Mai", + "Jun." : "Juin", + "Jul." : "Juil.", + "Aug." : "Août", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Déc.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Seuls les caractères suivants sont autorisés dans un nom d'utilisateur : \"a-z\", \"A-Z\", \"0-9\", \"_@-\" et \".\" (le point)", + "A valid username must be provided" : "Un nom d'utilisateur valide doit être saisi", + "Username contains whitespace at the beginning or at the end" : "Le nom d'utilisateur contient des espaces au début ou à la fin", + "Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points", + "Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur", + "A valid password must be provided" : "Un mot de passe valide doit être saisi", + "The username is already being used" : "Ce nom d'utilisateur est déjà utilisé", + "Could not create user" : "Impossible de créer l'utilisateur", + "User disabled" : "Utilisateur désactivé", + "Login canceled by app" : "L'authentification a été annulé par l'application", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", + "a safe home for all your data" : "un lieu sûr pour toutes vos données", + "File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard", + "Can't read file" : "Impossible de lire le fichier", + "Application is not enabled" : "L'application n'est pas activée", + "Authentication error" : "Erreur d'authentification", + "Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).", + "Cannot write into \"config\" directory" : "Impossible d’écrire dans le répertoire \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s", + "Cannot write into \"apps\" directory" : "Impossible d’écrire dans le répertoire \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant l'appstore dans le fichier de configuration.", + "Cannot create \"data\" directory" : "Impossible de créer le dossier \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Le problème de permissions peut généralement être résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s.", + "Setting locale to %s failed" : "Echec de la spécification des paramètres régionaux à %s", + "Please install one of these locales on your system and restart your webserver." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.", + "PHP module %s not installed." : "Le module PHP %s n’est pas installé.", + "Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.", + "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload est à \"%s\" alors que la valeur \"0\" est attendue", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Pour corriger ce problème mettez mbstring.func_overload à 0 dans votre php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 au moins est requis. Actuellement %s est installé.", + "To fix this issue update your libxml2 version and restart your web server." : "Pour régler ce problème, mettez à jour votre version de libxml2 et redémarrez votre serveur web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP semble configuré de manière à supprimer les blocs PHPdoc du code. Cela rendra plusieurs applications de base inaccessibles.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "La raison est probablement l'utilisation d'un cache / accélérateur tel que Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Les modules PHP ont été installés mais sont toujours indiqués comme manquants ?", + "Please ask your server administrator to restart the web server." : "Veuillez demander à votre administrateur serveur de redémarrer le serveur web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requis", + "Please upgrade your database version" : "Veuillez mettre à jour votre gestionnaire de base de données", + "Your data directory is readable by other users" : "Votre répertoire est lisible par les autres utilisateurs", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu ne puisse pas être listé par les autres utilisateurs.", + "Your data directory must be an absolute path" : "Le chemin de votre répertoire doit être un lien absolu", + "Check the value of \"datadirectory\" in your configuration" : "Verifiez la valeur de \"datadirectory\" dans votre configuration", + "Your data directory is invalid" : "Votre répertoire n'est pas valide", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assurez-vous que le répertoire de données contient un fichier \".ocdata\" à sa racine.", + "Action \"%s\" not supported or implemented." : "Action \"%s\" non supportée ou implémentée.", + "Authentication failed, wrong token or provider ID given" : "Échec de l'authentification, jeton erroné ou identification du fournisseur donnée", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Paramètres manquants pour compléter la requête. Paramètres manquants : \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'identifiant \"%1$s\" est déjà utilisé par le fournisseur de cloud \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Le fournisseur de cloud avec l'identifiant \"%s\" n'existe pas.", + "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur \"%s\".", + "Storage unauthorized. %s" : "Espace de stockage non autorisé. %s", + "Storage incomplete configuration. %s" : "Configuration de l'espace de stockage incomplète. %s", + "Storage connection error. %s" : "Erreur de connexion à l'espace stockage. %s", + "Storage is temporarily not available" : "Le support de stockage est temporairement indisponible", + "Storage connection timeout. %s" : "Le délai d'attente pour la connexion à l'espace de stockage a été dépassé. %s", + "Following databases are supported: %s" : "Les bases de données suivantes sont supportées : %s", + "Following platforms are supported: %s" : "Les plateformes suivantes sont prises en charge : %s", + "Overview" : "Vue d'ensemble", + "Basic settings" : "Paramètres de base", + "Sharing" : "Partage", + "Security" : "Sécurité", + "Groupware" : "Travail collaboratif", + "Personal info" : "Informations personnelles", + "Mobile & desktop" : "Mobile & bureau", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"apps\" ou en désactivant l'appstore dans le fichier de configuration. Voir %s" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/gl.js b/docker/overlays/nextcloud/html/lib/l10n/gl.js new file mode 100644 index 0000000..ee5d2f4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/gl.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Non é posíbel escribir no directorio «config»!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config»", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, se prefire manter o ficheiro «config.php» como de só lectura, marque a opción «config_is_read_only» como «true» nel.", + "See %s" : "Vexa %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config».", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, se prefire manter o ficheiro «config.php» como de só lectura, marque a opción «config_is_read_only» como «true» nel. Vexa %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os ficheiros da aplicación %1$s non foron substituídos correctamente. Asegúrese que é unha versión compatíbel co servidor.", + "Sample configuration detected" : "Detectouse a configuración de exemplo", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectouse que foi copiada a configuración de exemplo. Isto pode rachar a súa instalación e non é compatíbel. Lea a documentación antes de facer cambios en config.php", + "Other activities" : "Outras actividades", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edición para educación", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de software colaborativo", + "Hub bundle" : "Paquete de concentradores", + "Social sharing bundle" : "Paquete para compartir en redes sociais", + "PHP %s or higher is required." : "É necesario PHP %s ou superior.", + "PHP with a version lower than %s is required." : "É necesario PHP cunha versión inferior a %s.", + "%sbit or higher PHP required." : "É necesario PHP para %sbits ou superior.", + "The following architectures are supported: %s" : "Admítense as seguintes arquitecturas: %s", + "The following databases are supported: %s" : "Admítense as seguintes bases de datos: %s", + "The command line tool %s could not be found" : "Non foi posíbel atopar a ferramenta de liña de ordes %s", + "The library %s is not available." : "Non está dispoñíbel a biblioteca %s.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "É necesaria a biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "É necesaria a biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.", + "The following platforms are supported: %s" : "Admítense as seguintes plataformas: %s", + "Server version %s or higher is required." : "É necesaria a versión %s ou superior do servidor.", + "Server version %s or lower is required." : "É necesaria a versión %s ou inferior do servidor.", + "Logged in user must be an admin or sub admin" : "O usuario rexistrado debe ser un administrador ou subadministrador", + "Logged in user must be an admin" : "O usuario rexistrado debe ser un administrador", + "Wiping of device %s has started" : "Iniciouse a limpeza do dispositivo %s", + "Wiping of device »%s« has started" : "Iniciouse a limpeza do dispositivo «%s»", + "»%s« started remote wipe" : "«%s» iniciou a limpeza remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "O dispositivo ou aplicación «%s» iniciou o proceso remoto de limpeza. Recibirá outro correo cando remate o proceso", + "Wiping of device %s has finished" : "Rematou a limpeza do dispositivo %s", + "Wiping of device »%s« has finished" : "Rematou a limpeza do dispositivo «%s»", + "»%s« finished remote wipe" : "«%s» rematou a limpeza remota", + "Device or application »%s« has finished the remote wipe process." : "O dispositivo ou aplicación «%s» rematou o proceso remoto de limpeza.", + "Remote wipe started" : "Iniciouse a limpeza remota", + "A remote wipe was started on device %s" : "Iniciouse unha limpeza remota no dispositivo %s", + "Remote wipe finished" : "Rematou a limpeza remota", + "The remote wipe on %s has finished" : "A limpeza remota en %s rematou", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de ficheiro descoñecido", + "Invalid image" : "Imaxe incorrecta", + "Avatar image is not square" : "A imaxe do avatar non é un cadrado", + "today" : "hoxe", + "tomorrow" : "mañá", + "yesterday" : "onte", + "_in %n day_::_in %n days_" : ["nun día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n días"], + "next month" : "o vindeiro mes", + "last month" : "o mes pasado", + "_in %n month_::_in %n months_" : ["nun mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"], + "next year" : "o vindeiro ano", + "last year" : "o ano pasado", + "_in %n year_::_in %n years_" : ["nun ano","en %n anos"], + "_%n year ago_::_%n years ago_" : ["hai %n ano","hai %n anos"], + "_in %n hour_::_in %n hours_" : ["nunha hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n horas"], + "_in %n minute_::_in %n minutes_" : ["nun minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["hai %n minuto","hai %n minutos"], + "in a few seconds" : "en poucos segundos", + "seconds ago" : "hai uns segundos", + "Empty file" : "Ficheiro baleiro", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Non existe o módulo co ID: %s. Actíveo nos axustes das aplicacións ou contacte co administrador.", + "File name is a reserved word" : "O nome de ficheiro é unha palabra reservada", + "File name contains at least one invalid character" : "O nome de ficheiro contén algún carácter incorrecto", + "File name is too long" : "O nome de ficheiro é longo de máis", + "Dot files are not allowed" : "Non se admiten os ficheiros con punto", + "Empty filename is not allowed" : "Non está permitido deixar baleiro o nome de ficheiro", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Non é posíbel instalar a aplicación «%s» por mor de non poder ler o ficheiro appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Non é posíbel instalar a aplicación «%s» por mor de non ser compatíbel con esta versión do servidor.", + "__language_name__" : "Galego", + "This is an automatically sent email, please do not reply." : "Este é un correo enviado automaticamente, non responda.", + "Help" : "Axuda", + "Apps" : "Aplicacións", + "Settings" : "Axustes", + "Log out" : "Saír", + "Users" : "Usuarios", + "Unknown user" : "Usuario descoñecido", + "Additional settings" : "Axustes adicionais", + "%s enter the database username and name." : "%s introduza o nome de usuario e o nome da base de datos", + "%s enter the database username." : "%s introduza o nome de usuario da base de datos", + "%s enter the database name." : "%s introduza o nome da base de datos", + "%s you may not use dots in the database name" : "%s non se poden empregar puntos na base de datos", + "MySQL username and/or password not valid" : "Nome de usuario e/ou contrasinal de MySQL incorrecto", + "You need to enter details of an existing account." : "Debe introducir os detalles dunha conta existente.", + "Oracle connection could not be established" : "Non foi posíbel estabelecer a conexión con Oracle", + "Oracle username and/or password not valid" : "O nome de usuario e/ou contrasinal de Oracle é incorrecto", + "PostgreSQL username and/or password not valid" : "Nome de usuario e/ou contrasinal de PostgreSQL incorrecto", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X non é compatíbel e %s non funcionará correctamente nesta plataforma. Utilíceo baixo a súa responsabilidade!", + "For the best results, please consider using a GNU/Linux server instead." : "Para obter mellores resultados, considere o emprego dun servidor GNU/Linux no seu canto.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Semella que esta instancia de %s está a funcionar nun contorno PHP de 32 bisst e o open_basedir foi configurado no php.ini. Isto provocará problemas con ficheiros maiores de 4 GB e está absolutamente desaconsellado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Retire o axuste de open_basedir no php.ini ou cambie a PHP de 64 bits.", + "Set an admin username." : "Estabeleza un nome de usuario administrador", + "Set an admin password." : "Estabeleza un contrasinal de administrador", + "Can't create or write into the data directory %s" : "Non é posíbel crear ou escribir o directorio «data» %s", + "Invalid Federated Cloud ID" : "ID de nube federada incorrecto", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "A infraestrutura de compartición %s ten que implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Non se atopou a infraestrutura de compartición %s", + "Sharing backend for %s not found" : "Non se atopou a infraestrutura de compartición para %s", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con vostede e quere engadir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con vostede e quere engadir", + "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con vostede", + "Open »%s«" : "Abrir «%s»", + "%1$s via %2$s" : "%1$s mediante %2$s", + "You are not allowed to share %s" : "Non ten permiso para compartir %s", + "Can’t increase permissions of %s" : "Non é posíbel aumentar os permisos de %s", + "Files can’t be shared with delete permissions" : "Non é posíbel compartir ficheiros con permisos de eliminación", + "Files can’t be shared with create permissions" : "Non é posíbel compartir ficheiros con permisos de creación", + "Expiration date is in the past" : "Xa pasou a data de caducidade", + "Can’t set expiration date more than %s days in the future" : "Non é posíbel estabelecer a data de caducidade máis alo de %s días no futuro", + "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con vostede", + "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con vostede.", + "Click the button below to open it." : "Prema no botón de embaixo para abrilo.", + "The requested share does not exist anymore" : "O recurso compartido solicitado xa non existe", + "Could not find category \"%s\"" : "Non foi posíbel atopar a categoría «%s»", + "Sunday" : "domingo", + "Monday" : "luns", + "Tuesday" : "martes", + "Wednesday" : "mércores", + "Thursday" : "xoves", + "Friday" : "venres", + "Saturday" : "sábado", + "Sun." : "dom.", + "Mon." : "lun.", + "Tue." : "mar.", + "Wed." : "mér.", + "Thu." : "xov.", + "Fri." : "ven.", + "Sat." : "sáb.", + "Su" : "do", + "Mo" : "lu", + "Tu" : "ma", + "We" : "mé", + "Th" : "xo", + "Fr" : "ve", + "Sa" : "sá", + "January" : "xaneiro", + "February" : "febreiro", + "March" : "marzo", + "April" : "abril", + "May" : "maio", + "June" : "xuño", + "July" : "xullo", + "August" : "agosto", + "September" : "setembro", + "October" : "outubro", + "November" : "novembro", + "December" : "decembro", + "Jan." : "xan.", + "Feb." : "feb.", + "Mar." : "mar.", + "Apr." : "abr.", + "May." : "mai.", + "Jun." : "xuñ.", + "Jul." : "xul.", + "Aug." : "ago.", + "Sep." : "set.", + "Oct." : "out.", + "Nov." : "nov.", + "Dec." : "dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Só os seguintes caracteres están permitidos nos nomes de usuario: «a-z», «A-Z», «0-9» e «_.@-'»", + "A valid username must be provided" : "Debe fornecer un nome de usuario correcto", + "Username contains whitespace at the beginning or at the end" : "O nome de usuario contén un espazo en branco no inicio ou no final", + "Username must not consist of dots only" : "O nome de usuario non debe consistir só de puntos", + "Username is invalid because files already exist for this user" : "O nome de usuario non é válido porque xa existen ficheiros para este usuario", + "A valid password must be provided" : "Debe fornecer un contrasinal", + "The username is already being used" : "Este nome de usuario xa está a ser usado", + "Could not create user" : "Non foi posíbel crear o usuario", + "User disabled" : "Usuario desactivado", + "Login canceled by app" : "Acceso cancelado pola aplicación", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Non é posíbel instalar a aplicación «%1$s» por mor de non cumprirse as dependencias: %2$s", + "a safe home for all your data" : "un lugar seguro para todos os seus datos", + "File is currently busy, please try again later" : "O ficheiro está ocupado neste momento, ténteo máis adiante.", + "Can't read file" : "Non é posíbel ler o ficheiro", + "Application is not enabled" : "A aplicación non está activada", + "Authentication error" : "Produciuse un erro de autenticación", + "Token expired. Please reload page." : "Testemuño caducado. Recargue a páxina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Non hai controladores de base de datos (sqlite, mysql, ou postgresql) instalados.", + "Cannot write into \"config\" directory" : "Non é posíbel escribir no directorio «config»", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config». Vexa %s", + "Cannot write into \"apps\" directory" : "Non é posíbel escribir no directorio «apps»", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio de aplicacións ou desactivando a appstore no ficheiro de configuración.", + "Cannot create \"data\" directory" : "Non é posíbel crear o directorio «data»", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio raíz. Vexa %s.", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Polo xeral, pódense corrixir os permisos dándolle ao servidor web acceso de escritura ao directorio raíz. Vexa %s.", + "Setting locale to %s failed" : "Fallou o axuste da configuración local a %s", + "Please install one of these locales on your system and restart your webserver." : "Instale unha destas configuracións locais no seu sistema e reinicie o servidor web.", + "PHP module %s not installed." : "O módulo PHP %s non está instalado.", + "Please ask your server administrator to install the module." : "Pregúntelle ao administrador do servidor pola instalación do módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "O axuste de PHP «%s» non está estabelecido a «%s».", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Cambiar este axuste no ficheiro php.ini fará que Nextcloud funcione de novo", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está estabelecido a «%s» no canto do valor «0» agardado", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para arranxar esta incidencia, estabeleza mbstring.func_overload a 0 no ficheiro php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "É necesario, cando menos, libxml2 2.7.0. Actualmente esta instalado %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Para arranxar esta incidencia, actualice a versión de libxml2 e reinicie o servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Semella que PHP foi configurado para quitar bloques de documentos en liña. Isto fará que varias aplicacións sexan inaccesíbeis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto probabelmente se debe unha caché/acelerador como Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse os módulos de PHP, mais aínda aparecen listados como perdidos?", + "Please ask your server administrator to restart the web server." : "Pregúntelle ao administrador do servidor polo reinicio do servidor web..", + "PostgreSQL >= 9 required" : "É necesario PostgreSQL >= 9", + "Please upgrade your database version" : "Anove a versión da súa base de datos", + "Your data directory is readable by other users" : "O se directorio de datos é lexíbel por outros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Cambie os permisos a 0770 para que o directorio non poida ser listado por outros usuarios.", + "Your data directory must be an absolute path" : "O seu directorio de datos debe ser unha ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Comprobe o valor de «datadirectory» na configuración", + "Your data directory is invalid" : "O seu directorio de datos non é correcto", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrese de que existe un ficheiro chamado «.ocdata» na raíz do directorio de datos.", + "Action \"%s\" not supported or implemented." : "A acción «%s» non está admitida ou implementada.", + "Authentication failed, wrong token or provider ID given" : "Produciuse un fallo de autenticación. Deuse un testemuño ou un ID de provedor erróneos.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Faltan parámetros para completar a solicitude. Parámetros que faltan: «%s»", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "O ID «%1$s» xa está a ser usado polo provedor da nube federada «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "O provedor de nube federada co ID «%s» non existe.", + "Could not obtain lock type %d on \"%s\"." : "Non foi posíbel obter un bloqueo do tipo %d en «%s».", + "Storage unauthorized. %s" : "Almacenamento non autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta do almacenamento. %s", + "Storage connection error. %s" : "Produciuse un erro na conexión ao almacenamento. %s", + "Storage is temporarily not available" : "O almacenamento non está dispoñíbel temporalmente", + "Storage connection timeout. %s" : "Esgotouse o tempo de conexión co almacenamento. %s", + "Following databases are supported: %s" : "Admítense as seguintes bases de datos: %s", + "Following platforms are supported: %s" : "Admítense as seguintes plataformas: %s", + "Overview" : "Vista xeral", + "Basic settings" : "Axustes básicos", + "Sharing" : "Compartindo", + "Security" : "Seguridade", + "Groupware" : "Software colaborativo", + "Personal info" : "Información persoal", + "Mobile & desktop" : "Móbil e escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio das aplicacións ou desactivando a tenda de aplicacións no ficheiro de configuración. Vexa %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/gl.json b/docker/overlays/nextcloud/html/lib/l10n/gl.json new file mode 100644 index 0000000..a5f3b08 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/gl.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Non é posíbel escribir no directorio «config»!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config»", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, se prefire manter o ficheiro «config.php» como de só lectura, marque a opción «config_is_read_only» como «true» nel.", + "See %s" : "Vexa %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config».", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, se prefire manter o ficheiro «config.php» como de só lectura, marque a opción «config_is_read_only» como «true» nel. Vexa %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os ficheiros da aplicación %1$s non foron substituídos correctamente. Asegúrese que é unha versión compatíbel co servidor.", + "Sample configuration detected" : "Detectouse a configuración de exemplo", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectouse que foi copiada a configuración de exemplo. Isto pode rachar a súa instalación e non é compatíbel. Lea a documentación antes de facer cambios en config.php", + "Other activities" : "Outras actividades", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edición para educación", + "Enterprise bundle" : "Paquete empresarial", + "Groupware bundle" : "Paquete de software colaborativo", + "Hub bundle" : "Paquete de concentradores", + "Social sharing bundle" : "Paquete para compartir en redes sociais", + "PHP %s or higher is required." : "É necesario PHP %s ou superior.", + "PHP with a version lower than %s is required." : "É necesario PHP cunha versión inferior a %s.", + "%sbit or higher PHP required." : "É necesario PHP para %sbits ou superior.", + "The following architectures are supported: %s" : "Admítense as seguintes arquitecturas: %s", + "The following databases are supported: %s" : "Admítense as seguintes bases de datos: %s", + "The command line tool %s could not be found" : "Non foi posíbel atopar a ferramenta de liña de ordes %s", + "The library %s is not available." : "Non está dispoñíbel a biblioteca %s.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "É necesaria a biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "É necesaria a biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.", + "The following platforms are supported: %s" : "Admítense as seguintes plataformas: %s", + "Server version %s or higher is required." : "É necesaria a versión %s ou superior do servidor.", + "Server version %s or lower is required." : "É necesaria a versión %s ou inferior do servidor.", + "Logged in user must be an admin or sub admin" : "O usuario rexistrado debe ser un administrador ou subadministrador", + "Logged in user must be an admin" : "O usuario rexistrado debe ser un administrador", + "Wiping of device %s has started" : "Iniciouse a limpeza do dispositivo %s", + "Wiping of device »%s« has started" : "Iniciouse a limpeza do dispositivo «%s»", + "»%s« started remote wipe" : "«%s» iniciou a limpeza remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "O dispositivo ou aplicación «%s» iniciou o proceso remoto de limpeza. Recibirá outro correo cando remate o proceso", + "Wiping of device %s has finished" : "Rematou a limpeza do dispositivo %s", + "Wiping of device »%s« has finished" : "Rematou a limpeza do dispositivo «%s»", + "»%s« finished remote wipe" : "«%s» rematou a limpeza remota", + "Device or application »%s« has finished the remote wipe process." : "O dispositivo ou aplicación «%s» rematou o proceso remoto de limpeza.", + "Remote wipe started" : "Iniciouse a limpeza remota", + "A remote wipe was started on device %s" : "Iniciouse unha limpeza remota no dispositivo %s", + "Remote wipe finished" : "Rematou a limpeza remota", + "The remote wipe on %s has finished" : "A limpeza remota en %s rematou", + "Authentication" : "Autenticación", + "Unknown filetype" : "Tipo de ficheiro descoñecido", + "Invalid image" : "Imaxe incorrecta", + "Avatar image is not square" : "A imaxe do avatar non é un cadrado", + "today" : "hoxe", + "tomorrow" : "mañá", + "yesterday" : "onte", + "_in %n day_::_in %n days_" : ["nun día","en %n días"], + "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n días"], + "next month" : "o vindeiro mes", + "last month" : "o mes pasado", + "_in %n month_::_in %n months_" : ["nun mes","en %n meses"], + "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"], + "next year" : "o vindeiro ano", + "last year" : "o ano pasado", + "_in %n year_::_in %n years_" : ["nun ano","en %n anos"], + "_%n year ago_::_%n years ago_" : ["hai %n ano","hai %n anos"], + "_in %n hour_::_in %n hours_" : ["nunha hora","en %n horas"], + "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n horas"], + "_in %n minute_::_in %n minutes_" : ["nun minuto","en %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["hai %n minuto","hai %n minutos"], + "in a few seconds" : "en poucos segundos", + "seconds ago" : "hai uns segundos", + "Empty file" : "Ficheiro baleiro", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Non existe o módulo co ID: %s. Actíveo nos axustes das aplicacións ou contacte co administrador.", + "File name is a reserved word" : "O nome de ficheiro é unha palabra reservada", + "File name contains at least one invalid character" : "O nome de ficheiro contén algún carácter incorrecto", + "File name is too long" : "O nome de ficheiro é longo de máis", + "Dot files are not allowed" : "Non se admiten os ficheiros con punto", + "Empty filename is not allowed" : "Non está permitido deixar baleiro o nome de ficheiro", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Non é posíbel instalar a aplicación «%s» por mor de non poder ler o ficheiro appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Non é posíbel instalar a aplicación «%s» por mor de non ser compatíbel con esta versión do servidor.", + "__language_name__" : "Galego", + "This is an automatically sent email, please do not reply." : "Este é un correo enviado automaticamente, non responda.", + "Help" : "Axuda", + "Apps" : "Aplicacións", + "Settings" : "Axustes", + "Log out" : "Saír", + "Users" : "Usuarios", + "Unknown user" : "Usuario descoñecido", + "Additional settings" : "Axustes adicionais", + "%s enter the database username and name." : "%s introduza o nome de usuario e o nome da base de datos", + "%s enter the database username." : "%s introduza o nome de usuario da base de datos", + "%s enter the database name." : "%s introduza o nome da base de datos", + "%s you may not use dots in the database name" : "%s non se poden empregar puntos na base de datos", + "MySQL username and/or password not valid" : "Nome de usuario e/ou contrasinal de MySQL incorrecto", + "You need to enter details of an existing account." : "Debe introducir os detalles dunha conta existente.", + "Oracle connection could not be established" : "Non foi posíbel estabelecer a conexión con Oracle", + "Oracle username and/or password not valid" : "O nome de usuario e/ou contrasinal de Oracle é incorrecto", + "PostgreSQL username and/or password not valid" : "Nome de usuario e/ou contrasinal de PostgreSQL incorrecto", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X non é compatíbel e %s non funcionará correctamente nesta plataforma. Utilíceo baixo a súa responsabilidade!", + "For the best results, please consider using a GNU/Linux server instead." : "Para obter mellores resultados, considere o emprego dun servidor GNU/Linux no seu canto.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Semella que esta instancia de %s está a funcionar nun contorno PHP de 32 bisst e o open_basedir foi configurado no php.ini. Isto provocará problemas con ficheiros maiores de 4 GB e está absolutamente desaconsellado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Retire o axuste de open_basedir no php.ini ou cambie a PHP de 64 bits.", + "Set an admin username." : "Estabeleza un nome de usuario administrador", + "Set an admin password." : "Estabeleza un contrasinal de administrador", + "Can't create or write into the data directory %s" : "Non é posíbel crear ou escribir o directorio «data» %s", + "Invalid Federated Cloud ID" : "ID de nube federada incorrecto", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "A infraestrutura de compartición %s ten que implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Non se atopou a infraestrutura de compartición %s", + "Sharing backend for %s not found" : "Non se atopou a infraestrutura de compartición para %s", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con vostede e quere engadir:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con vostede e quere engadir", + "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con vostede", + "Open »%s«" : "Abrir «%s»", + "%1$s via %2$s" : "%1$s mediante %2$s", + "You are not allowed to share %s" : "Non ten permiso para compartir %s", + "Can’t increase permissions of %s" : "Non é posíbel aumentar os permisos de %s", + "Files can’t be shared with delete permissions" : "Non é posíbel compartir ficheiros con permisos de eliminación", + "Files can’t be shared with create permissions" : "Non é posíbel compartir ficheiros con permisos de creación", + "Expiration date is in the past" : "Xa pasou a data de caducidade", + "Can’t set expiration date more than %s days in the future" : "Non é posíbel estabelecer a data de caducidade máis alo de %s días no futuro", + "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con vostede", + "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con vostede.", + "Click the button below to open it." : "Prema no botón de embaixo para abrilo.", + "The requested share does not exist anymore" : "O recurso compartido solicitado xa non existe", + "Could not find category \"%s\"" : "Non foi posíbel atopar a categoría «%s»", + "Sunday" : "domingo", + "Monday" : "luns", + "Tuesday" : "martes", + "Wednesday" : "mércores", + "Thursday" : "xoves", + "Friday" : "venres", + "Saturday" : "sábado", + "Sun." : "dom.", + "Mon." : "lun.", + "Tue." : "mar.", + "Wed." : "mér.", + "Thu." : "xov.", + "Fri." : "ven.", + "Sat." : "sáb.", + "Su" : "do", + "Mo" : "lu", + "Tu" : "ma", + "We" : "mé", + "Th" : "xo", + "Fr" : "ve", + "Sa" : "sá", + "January" : "xaneiro", + "February" : "febreiro", + "March" : "marzo", + "April" : "abril", + "May" : "maio", + "June" : "xuño", + "July" : "xullo", + "August" : "agosto", + "September" : "setembro", + "October" : "outubro", + "November" : "novembro", + "December" : "decembro", + "Jan." : "xan.", + "Feb." : "feb.", + "Mar." : "mar.", + "Apr." : "abr.", + "May." : "mai.", + "Jun." : "xuñ.", + "Jul." : "xul.", + "Aug." : "ago.", + "Sep." : "set.", + "Oct." : "out.", + "Nov." : "nov.", + "Dec." : "dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Só os seguintes caracteres están permitidos nos nomes de usuario: «a-z», «A-Z», «0-9» e «_.@-'»", + "A valid username must be provided" : "Debe fornecer un nome de usuario correcto", + "Username contains whitespace at the beginning or at the end" : "O nome de usuario contén un espazo en branco no inicio ou no final", + "Username must not consist of dots only" : "O nome de usuario non debe consistir só de puntos", + "Username is invalid because files already exist for this user" : "O nome de usuario non é válido porque xa existen ficheiros para este usuario", + "A valid password must be provided" : "Debe fornecer un contrasinal", + "The username is already being used" : "Este nome de usuario xa está a ser usado", + "Could not create user" : "Non foi posíbel crear o usuario", + "User disabled" : "Usuario desactivado", + "Login canceled by app" : "Acceso cancelado pola aplicación", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Non é posíbel instalar a aplicación «%1$s» por mor de non cumprirse as dependencias: %2$s", + "a safe home for all your data" : "un lugar seguro para todos os seus datos", + "File is currently busy, please try again later" : "O ficheiro está ocupado neste momento, ténteo máis adiante.", + "Can't read file" : "Non é posíbel ler o ficheiro", + "Application is not enabled" : "A aplicación non está activada", + "Authentication error" : "Produciuse un erro de autenticación", + "Token expired. Please reload page." : "Testemuño caducado. Recargue a páxina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Non hai controladores de base de datos (sqlite, mysql, ou postgresql) instalados.", + "Cannot write into \"config\" directory" : "Non é posíbel escribir no directorio «config»", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Polo xeral, isto pode ser fixado para permitirlle ao servidor web acceso de escritura ao directorio «config». Vexa %s", + "Cannot write into \"apps\" directory" : "Non é posíbel escribir no directorio «apps»", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio de aplicacións ou desactivando a appstore no ficheiro de configuración.", + "Cannot create \"data\" directory" : "Non é posíbel crear o directorio «data»", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio raíz. Vexa %s.", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Polo xeral, pódense corrixir os permisos dándolle ao servidor web acceso de escritura ao directorio raíz. Vexa %s.", + "Setting locale to %s failed" : "Fallou o axuste da configuración local a %s", + "Please install one of these locales on your system and restart your webserver." : "Instale unha destas configuracións locais no seu sistema e reinicie o servidor web.", + "PHP module %s not installed." : "O módulo PHP %s non está instalado.", + "Please ask your server administrator to install the module." : "Pregúntelle ao administrador do servidor pola instalación do módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "O axuste de PHP «%s» non está estabelecido a «%s».", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Cambiar este axuste no ficheiro php.ini fará que Nextcloud funcione de novo", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está estabelecido a «%s» no canto do valor «0» agardado", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para arranxar esta incidencia, estabeleza mbstring.func_overload a 0 no ficheiro php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "É necesario, cando menos, libxml2 2.7.0. Actualmente esta instalado %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Para arranxar esta incidencia, actualice a versión de libxml2 e reinicie o servidor web. ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Semella que PHP foi configurado para quitar bloques de documentos en liña. Isto fará que varias aplicacións sexan inaccesíbeis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto probabelmente se debe unha caché/acelerador como Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse os módulos de PHP, mais aínda aparecen listados como perdidos?", + "Please ask your server administrator to restart the web server." : "Pregúntelle ao administrador do servidor polo reinicio do servidor web..", + "PostgreSQL >= 9 required" : "É necesario PostgreSQL >= 9", + "Please upgrade your database version" : "Anove a versión da súa base de datos", + "Your data directory is readable by other users" : "O se directorio de datos é lexíbel por outros usuarios", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Cambie os permisos a 0770 para que o directorio non poida ser listado por outros usuarios.", + "Your data directory must be an absolute path" : "O seu directorio de datos debe ser unha ruta absoluta", + "Check the value of \"datadirectory\" in your configuration" : "Comprobe o valor de «datadirectory» na configuración", + "Your data directory is invalid" : "O seu directorio de datos non é correcto", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrese de que existe un ficheiro chamado «.ocdata» na raíz do directorio de datos.", + "Action \"%s\" not supported or implemented." : "A acción «%s» non está admitida ou implementada.", + "Authentication failed, wrong token or provider ID given" : "Produciuse un fallo de autenticación. Deuse un testemuño ou un ID de provedor erróneos.", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Faltan parámetros para completar a solicitude. Parámetros que faltan: «%s»", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "O ID «%1$s» xa está a ser usado polo provedor da nube federada «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "O provedor de nube federada co ID «%s» non existe.", + "Could not obtain lock type %d on \"%s\"." : "Non foi posíbel obter un bloqueo do tipo %d en «%s».", + "Storage unauthorized. %s" : "Almacenamento non autorizado. %s", + "Storage incomplete configuration. %s" : "Configuración incompleta do almacenamento. %s", + "Storage connection error. %s" : "Produciuse un erro na conexión ao almacenamento. %s", + "Storage is temporarily not available" : "O almacenamento non está dispoñíbel temporalmente", + "Storage connection timeout. %s" : "Esgotouse o tempo de conexión co almacenamento. %s", + "Following databases are supported: %s" : "Admítense as seguintes bases de datos: %s", + "Following platforms are supported: %s" : "Admítense as seguintes plataformas: %s", + "Overview" : "Vista xeral", + "Basic settings" : "Axustes básicos", + "Sharing" : "Compartindo", + "Security" : "Seguridade", + "Groupware" : "Software colaborativo", + "Personal info" : "Información persoal", + "Mobile & desktop" : "Móbil e escritorio", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Polo xeral, isto pódese solucionar dándolle ao servidor web acceso de escritura ao directorio das aplicacións ou desactivando a tenda de aplicacións no ficheiro de configuración. Vexa %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/he.js b/docker/overlays/nextcloud/html/lib/l10n/he.js new file mode 100644 index 0000000..ec506d7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/he.js @@ -0,0 +1,209 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "לא ניתן לכתוב לתיקיית „config”!", + "This can usually be fixed by giving the webserver write access to the config directory" : "בדרך כלל ניתן לפתור את הבעיה על ידי כך שנותנים לתכנית השרת הרשאות כתיבה לתיקיית config", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "או, אם עדיף לך לשמור על config.php לקריאה בלבד, ניתן להגדיר את האפשרות „config_is_read_only” לערך true.", + "See %s" : "יש לעיין ב־%s", + "This can usually be fixed by giving the webserver write access to the config directory." : "בדרך כלל ניתן לתקן זאת על ידי מתן גישת כתיבה לשרת לתיקיית ההגדרות (config).", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "או, אם עדיף לך לשמור על config.php לקריאה בלבד, ניתן להגדיר את האפשרות „config_is_read_only” לערך true. נא לעיין ב־%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "הקבצים של היישומון %1$s לא מוקמו במקום הנכון. נא לוודא שזו גרסה שהשרת תומך בה.", + "Sample configuration detected" : "התגלתה דוגמת תצורה", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "התגלה שדוגמת התצורה הועתקה. דבר זה עלול לשבור את ההתקנה ולא נתמך.יש לקרוא את מסמכי התיעוד לפני שמבצעים שינויים ב- config.php", + "Education Edition" : "מהדורה חינוכית", + "Enterprise bundle" : "מהדורה מסחרית", + "Groupware bundle" : "מהדורה קבוצתית", + "Social sharing bundle" : "מאגד שיתוף חברתי", + "PHP %s or higher is required." : "נדרש PHP בגרסת %s ומעלה.", + "PHP with a version lower than %s is required." : "נדרש PHP בגרסה נמוכה מ- %s.", + "%sbit or higher PHP required." : "נדרש PHP בגרסת %s ומעלה.", + "The following databases are supported: %s" : "יש תמיכה במסדי הנתונים הבאים: %s", + "The command line tool %s could not be found" : "כלי שורת הפקודה %s לא אותר", + "The library %s is not available." : "הספריה %s אינה זמינה.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "נדרשת ספרייה %1$s עם גרסה מתחת ל־%2$s - הגרסה הזמינה היא %3$s.", + "Server version %s or higher is required." : "נדרשת גרסה שרת %s ומעלה.", + "Server version %s or lower is required." : "נדרשת גרסה שרת %s ומטה.", + "Logged in user must be an admin" : "על המשתמש שנכנס להיות מנהל", + "Authentication" : "אימות", + "Unknown filetype" : "סוג קובץ לא מוכר", + "Invalid image" : "תמונה לא חוקית", + "Avatar image is not square" : "התמונה המייצגת אינה מרובעת", + "today" : "היום", + "tomorrow" : "מחר", + "yesterday" : "אתמול", + "_in %n day_::_in %n days_" : ["בעוד יום","בעוד יומיים","בעוד %n ימים","בעוד %n ימים"], + "_%n day ago_::_%n days ago_" : ["לפני %n יום","לפני %n ימים","לפני %n ימים","לפני %n ימים"], + "next month" : "בחודש הבא", + "last month" : "חודש שעבר", + "_in %n month_::_in %n months_" : ["בעוד חודש","בעוד חודשיים","בעוד %n חודשים","בעוד %n חודשים"], + "_%n month ago_::_%n months ago_" : ["לפני חודש","לפני חודשיים","לפני %n חודשים","לפני %n חודשים"], + "next year" : "בשנה הבאה", + "last year" : "שנה שעברה", + "_in %n year_::_in %n years_" : ["בעוד שנה","בעוד שנתיים","בעוד %n שנים","בעוד %n שנים"], + "_%n year ago_::_%n years ago_" : ["לפני %n שנה","לפני %n שנים","לפני %n שנים","לפני %n שנים"], + "_in %n hour_::_in %n hours_" : ["בעוד שעה","בעוד שעתיים","בעוד %n שעות","בעוד %n שעות"], + "_%n hour ago_::_%n hours ago_" : ["לפני שעה","לפני שעתיים","לפני %n שעות","לפני %n שעות"], + "_in %n minute_::_in %n minutes_" : ["בעוד דקה","בעוד 2 דקות","בעוד %n דקות","בעוד %n דקות"], + "_%n minute ago_::_%n minutes ago_" : ["לפני דקה","לפני 2 דקות","לפני %n דקות","לפני %n דקות"], + "in a few seconds" : "בעוד מספר שניות", + "seconds ago" : "שניות", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "המודול עם המזהה: %s לא קיים. נא להפעיל אותו בהגדרות היישומונים שלך או ליצור קשר עם מנהל המערכת.", + "File name is a reserved word" : "שם קובץ הוא מילה שמורה", + "File name contains at least one invalid character" : "שם הקובץ כולל לפחות תו אחד לא חוקי", + "File name is too long" : "שם קובץ ארוך מדי", + "Dot files are not allowed" : "אסור ששמות קבצים יתחילו בנקודה", + "Empty filename is not allowed" : "שם קובץ ריק אינו מאושר", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "יישום \"%s\" לא ניתן להתקנה כיוון שקובץ appinfo לא ניתן לקריאה.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "לא ניתן להתקין את היישומון „%s” כיוון שאין לו תמיכה בגרסה זו של השרת.", + "__language_name__" : "עברית", + "This is an automatically sent email, please do not reply." : "זו הודעת דוא״ל שנשלחה אוטומטית, נא לא להגיב.", + "Help" : "עזרה", + "Apps" : "יישומים", + "Settings" : "הגדרות", + "Log out" : "התנתק", + "Users" : "משתמשים", + "Unknown user" : "משתמש לא ידוע", + "Additional settings" : "הגדרות נוספות", + "%s enter the database username and name." : "%s יש להכניס את שם המשתמש ושם מסד הנתונים.", + "%s enter the database username." : "%s נכנס למסד נתוני שמות המשתמשים.", + "%s enter the database name." : "%s נכנס למסד נתוני השמות.", + "%s you may not use dots in the database name" : "%s לא ניתן להשתמש בנקודות בשם מסד הנתונים", + "You need to enter details of an existing account." : "עליך להקליד פרטים של חשבון קיים.", + "Oracle connection could not be established" : "לא ניתן היה ליצור חיבור Oracle", + "Oracle username and/or password not valid" : "שם משתמש ו/או סיסמת Oracle אינם תקפים", + "PostgreSQL username and/or password not valid" : "שם משתמש ו/או סיסמת PostgreSQL אינם תקפים", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X אינו נתמך ו- %s לא יעבוד כשורה בפלטפורמה זו. ניתן לקחת סיכון ולהשתמש באחריותך! ", + "For the best results, please consider using a GNU/Linux server instead." : "לתוצאות הכי טובות, יש לשקול שימוש בשרת GNU/Linux במקום.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "נראה ש- %s עובד על בסיס סביבת 32-bit PHP ושה- open_basedir הוגדר בקובץ php.ini. מצב זה יוביל לבעיות עם קבצים הגדולים מ- 4 GB ואינו מומלץ לחלוטין.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "יש להסיר את הגדרת open_basedir מתוך קובץ php.ini או להחליף לסביבת 64-bit PHP.", + "Set an admin username." : "קביעת שם משתמש מנהל", + "Set an admin password." : "קביעת סיסמת מנהל", + "Can't create or write into the data directory %s" : "לא ניתן ליצור או לכתוב לתוך תיקיית הנתונים %s", + "Invalid Federated Cloud ID" : "זיהוי ענן מאוגד לא חוקי", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "צד אחורי לשיתוף %s חייב ליישם את ממשק OCP\\Share_Backend", + "Sharing backend %s not found" : "צד אחורי לשיתוף %s לא נמצא", + "Sharing backend for %s not found" : "צד אחורי לשיתוף של %s לא נמצא", + "»%s« added a note to a file shared with you" : "התווספה הערה על קובץ ששותף את על ידי „%s”", + "Open »%s«" : "פתיחת „%s”", + "%1$s via %2$s" : "%1$s דרך %2$s", + "You are not allowed to share %s" : "אינך רשאי/ת לשתף %s", + "Can’t increase permissions of %s" : "לא ניתן לחזק את ההרשאות של %s", + "Files can’t be shared with delete permissions" : "לא ניתן לשתף קבצים עם הרשאת מחיקה", + "Files can’t be shared with create permissions" : "לא ניתן לשתף קבצים עם הרשאות יצירה", + "Expiration date is in the past" : "תאריך תפוגה הנו בעבר", + "Can’t set expiration date more than %s days in the future" : "לא ניתן להגדיר את תאריך התפוגה מעל %s ימים בעתיד", + "%1$s shared »%2$s« with you" : "%2$s שותף אתך על ידי %1$s", + "%1$s shared »%2$s« with you." : "„%2$s” שותף אתך על ידי %1$s.", + "Click the button below to open it." : "יש ללחוץ על הכפתור להלן כדי לפתוח אותו.", + "The requested share does not exist anymore" : "השיתוף המבוקש אינו קיים עוד", + "Could not find category \"%s\"" : "לא ניתן למצוא את הקטגוריה „%s“", + "Sunday" : "יום ראשון", + "Monday" : "יום שני", + "Tuesday" : "יום שלישי", + "Wednesday" : "יום רביעי", + "Thursday" : "יום חמישי", + "Friday" : "יום שישי", + "Saturday" : "שבת", + "Sun." : "ראשון", + "Mon." : "שני", + "Tue." : "שלישי", + "Wed." : "רביעי", + "Thu." : "חמישי", + "Fri." : "שישי", + "Sat." : "שבת", + "Su" : "א", + "Mo" : "ב", + "Tu" : "ג", + "We" : "ד", + "Th" : "ה", + "Fr" : "ו", + "Sa" : "ש", + "January" : "ינואר", + "February" : "פברואר", + "March" : "מרץ", + "April" : "אפריל", + "May" : "מאי", + "June" : "יוני", + "July" : "יולי", + "August" : "אוגוסט", + "September" : "ספטמבר", + "October" : "אוקטובר", + "November" : "נובמבר", + "December" : "דצמבר", + "Jan." : "ינו׳", + "Feb." : "פבר׳", + "Mar." : "מרץ", + "Apr." : "אפר׳", + "May." : "מאי", + "Jun." : "יונ׳", + "Jul." : "יול׳", + "Aug." : "אוג׳", + "Sep." : "ספט׳", + "Oct." : "אוק׳", + "Nov." : "נוב׳", + "Dec." : "דצמ׳", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "רק התווים הבאים מאושרים לשם משתמש: \"a-z\", \"A-Z\", \"0-9\", וגם \"_.@-'\"", + "A valid username must be provided" : "יש לספק שם משתמש תקני", + "Username contains whitespace at the beginning or at the end" : "שם המשתמש מכיל רווח בתחילתו או בסופו", + "Username must not consist of dots only" : "שם המשתמש לא יכול להיות מורכב מנקודות בלבד", + "A valid password must be provided" : "יש לספק ססמה תקנית", + "The username is already being used" : "השם משתמש כבר בשימוש", + "Could not create user" : "לא ניתן ליצור משתמש", + "User disabled" : "משתמש מנוטרל", + "Login canceled by app" : "התחברות בוטלה על ידי יישום", + "a safe home for all your data" : "בית בטוח עבור כל המידע שלך", + "File is currently busy, please try again later" : "הקובץ בשימוש כרגע, יש לנסות שוב מאוחר יותר", + "Can't read file" : "לא ניתן לקרוא קובץ", + "Application is not enabled" : "יישומים אינם מופעלים", + "Authentication error" : "שגיאת הזדהות", + "Token expired. Please reload page." : "פג תוקף. נא לטעון שוב את הדף.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "לא מותקנים דרייברים למסד הנתונים (sqlite, mysql, או postgresql).", + "Cannot write into \"config\" directory" : "לא ניתן לכתוב לתיקיית \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית ההגדרות. נא לעיין ב־%s", + "Cannot write into \"apps\" directory" : "לא ניתן לכתוב לתיקיית \"apps\"", + "Cannot create \"data\" directory" : "לא ניתן ליצור תיקיית „data”", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית הבסיס. נא לעיין ב־%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "בדרך כלל ניתן לתקן הרשאות על ידי מתן גישה לשרת שלך אל תיקיית העל. נא לעיין ב־%s.", + "Setting locale to %s failed" : "הגדרת שפה ל- %s נכשלה", + "Please install one of these locales on your system and restart your webserver." : "יש להתקין אחת מהשפות על המערכת שלך ולהפעיל מחדש את שרת האינטרנט.", + "PHP module %s not installed." : "מודול PHP %s אינו מותקן.", + "Please ask your server administrator to install the module." : "יש לבקש ממנהל השרת שלך להתקין את המודול.", + "PHP setting \"%s\" is not set to \"%s\"." : "הגדרות PHP \"%s\" אינם מוגדרות ל- \"%s\"", + "Adjusting this setting in php.ini will make Nextcloud run again" : "התאמת ההגדרה הזו ב־php.ini יאפשר ל־Nextcloud לפעול שוב", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload מוגדר ל- \"%s\" במקום הערך המצופה \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "לתיקון בעיה זו יש להגדיר mbstring.func_overload כ- 0 iבקובץ ה- php.ini שלך", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 נדרש לכל הפחות. כרגע %s מותקן.", + "To fix this issue update your libxml2 version and restart your web server." : "לתיקון הבעיה יש לעדכן את גרסת ה- libxml2 שלך ולהפעיל מחדש את שרת האינטרנט שלך.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ככל הנראה מוגדר ל- strip inline doc blocks. זה יגרום למספר יישומי ליבה לא להיות נגישים.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "זה ככל הנראה נגרם על ידי מאיץ/מטמון כמו Zend OPcache או eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "מודולי PHP הותקנו, אך עדיין רשומים כחסרים?", + "Please ask your server administrator to restart the web server." : "יש לבקש ממנהל השרת שלך להפעיל מחדש את שרת האינטרנט.", + "PostgreSQL >= 9 required" : "נדרש PostgreSQL >= 9", + "Please upgrade your database version" : "יש לשדרג את גרסת מסד הנתונים שלך", + "Your data directory is readable by other users" : "תיקיית הנתונים שלך ניתנת לקריאה על ידי משתמשים אחרים", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "יש לשנות את ההרשאות ל- 0770 כך שהתיקייה לא תרשם על ידי משתמשים אחרים.", + "Your data directory must be an absolute path" : "תיקיית הנתונים שלך חייבת להיות במבנה של נתיב מלא (מיקום מוחלט)", + "Check the value of \"datadirectory\" in your configuration" : "יש לבדוק את הערך \"datadirectory\" בהגדרות התצורה שלך", + "Your data directory is invalid" : "תיקיית הנתונים שלך שגויה", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "נא לוודא שיש קובץ בשם „‎.ocdata” בבסיס תיקיית הנתונים שלך.", + "Action \"%s\" not supported or implemented." : "הפעולה „%s” אינה נתמכת או שאינה מוטמעת.", + "Authentication failed, wrong token or provider ID given" : "האימות נכשל, האסימון או מזהה הספק שסופקו שגויים", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "חסרים משתנים להשלמת הבקשה. המשתנים החסרים: „%s”", + "Could not obtain lock type %d on \"%s\"." : "לא ניתן היה להשיג סוג נעילה %d ב- \"%s\".", + "Storage unauthorized. %s" : "אחסון לא מורשה. %s", + "Storage incomplete configuration. %s" : "תצורה לא מושלמת של האחסון. %s", + "Storage connection error. %s" : "שגיאת חיבור אחסון. %s", + "Storage is temporarily not available" : "האחסון אינו זמין כרגע", + "Storage connection timeout. %s" : "פסק זמן חיבור אחסון. %s", + "Following databases are supported: %s" : "מסדי הנתונים הבאים נתמכים: %s", + "Following platforms are supported: %s" : "הפלטפורמות הבאות נתמכות: %s", + "Overview" : "סקירה", + "Basic settings" : "הגדרות בסיסיות", + "Sharing" : "שיתוף", + "Security" : "אבטחה", + "Groupware" : "קבוצתי", + "Personal info" : "פרטים אישיים", + "Mobile & desktop" : "נייד ושולחן עבודה", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית היישומונים או להשבית את חנות היישומונים בקובץ ההגדרות. נא לעיין ב־%s" +}, +"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/he.json b/docker/overlays/nextcloud/html/lib/l10n/he.json new file mode 100644 index 0000000..d6fc84a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/he.json @@ -0,0 +1,207 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "לא ניתן לכתוב לתיקיית „config”!", + "This can usually be fixed by giving the webserver write access to the config directory" : "בדרך כלל ניתן לפתור את הבעיה על ידי כך שנותנים לתכנית השרת הרשאות כתיבה לתיקיית config", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "או, אם עדיף לך לשמור על config.php לקריאה בלבד, ניתן להגדיר את האפשרות „config_is_read_only” לערך true.", + "See %s" : "יש לעיין ב־%s", + "This can usually be fixed by giving the webserver write access to the config directory." : "בדרך כלל ניתן לתקן זאת על ידי מתן גישת כתיבה לשרת לתיקיית ההגדרות (config).", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "או, אם עדיף לך לשמור על config.php לקריאה בלבד, ניתן להגדיר את האפשרות „config_is_read_only” לערך true. נא לעיין ב־%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "הקבצים של היישומון %1$s לא מוקמו במקום הנכון. נא לוודא שזו גרסה שהשרת תומך בה.", + "Sample configuration detected" : "התגלתה דוגמת תצורה", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "התגלה שדוגמת התצורה הועתקה. דבר זה עלול לשבור את ההתקנה ולא נתמך.יש לקרוא את מסמכי התיעוד לפני שמבצעים שינויים ב- config.php", + "Education Edition" : "מהדורה חינוכית", + "Enterprise bundle" : "מהדורה מסחרית", + "Groupware bundle" : "מהדורה קבוצתית", + "Social sharing bundle" : "מאגד שיתוף חברתי", + "PHP %s or higher is required." : "נדרש PHP בגרסת %s ומעלה.", + "PHP with a version lower than %s is required." : "נדרש PHP בגרסה נמוכה מ- %s.", + "%sbit or higher PHP required." : "נדרש PHP בגרסת %s ומעלה.", + "The following databases are supported: %s" : "יש תמיכה במסדי הנתונים הבאים: %s", + "The command line tool %s could not be found" : "כלי שורת הפקודה %s לא אותר", + "The library %s is not available." : "הספריה %s אינה זמינה.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "נדרשת ספרייה %1$s עם גרסה מתחת ל־%2$s - הגרסה הזמינה היא %3$s.", + "Server version %s or higher is required." : "נדרשת גרסה שרת %s ומעלה.", + "Server version %s or lower is required." : "נדרשת גרסה שרת %s ומטה.", + "Logged in user must be an admin" : "על המשתמש שנכנס להיות מנהל", + "Authentication" : "אימות", + "Unknown filetype" : "סוג קובץ לא מוכר", + "Invalid image" : "תמונה לא חוקית", + "Avatar image is not square" : "התמונה המייצגת אינה מרובעת", + "today" : "היום", + "tomorrow" : "מחר", + "yesterday" : "אתמול", + "_in %n day_::_in %n days_" : ["בעוד יום","בעוד יומיים","בעוד %n ימים","בעוד %n ימים"], + "_%n day ago_::_%n days ago_" : ["לפני %n יום","לפני %n ימים","לפני %n ימים","לפני %n ימים"], + "next month" : "בחודש הבא", + "last month" : "חודש שעבר", + "_in %n month_::_in %n months_" : ["בעוד חודש","בעוד חודשיים","בעוד %n חודשים","בעוד %n חודשים"], + "_%n month ago_::_%n months ago_" : ["לפני חודש","לפני חודשיים","לפני %n חודשים","לפני %n חודשים"], + "next year" : "בשנה הבאה", + "last year" : "שנה שעברה", + "_in %n year_::_in %n years_" : ["בעוד שנה","בעוד שנתיים","בעוד %n שנים","בעוד %n שנים"], + "_%n year ago_::_%n years ago_" : ["לפני %n שנה","לפני %n שנים","לפני %n שנים","לפני %n שנים"], + "_in %n hour_::_in %n hours_" : ["בעוד שעה","בעוד שעתיים","בעוד %n שעות","בעוד %n שעות"], + "_%n hour ago_::_%n hours ago_" : ["לפני שעה","לפני שעתיים","לפני %n שעות","לפני %n שעות"], + "_in %n minute_::_in %n minutes_" : ["בעוד דקה","בעוד 2 דקות","בעוד %n דקות","בעוד %n דקות"], + "_%n minute ago_::_%n minutes ago_" : ["לפני דקה","לפני 2 דקות","לפני %n דקות","לפני %n דקות"], + "in a few seconds" : "בעוד מספר שניות", + "seconds ago" : "שניות", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "המודול עם המזהה: %s לא קיים. נא להפעיל אותו בהגדרות היישומונים שלך או ליצור קשר עם מנהל המערכת.", + "File name is a reserved word" : "שם קובץ הוא מילה שמורה", + "File name contains at least one invalid character" : "שם הקובץ כולל לפחות תו אחד לא חוקי", + "File name is too long" : "שם קובץ ארוך מדי", + "Dot files are not allowed" : "אסור ששמות קבצים יתחילו בנקודה", + "Empty filename is not allowed" : "שם קובץ ריק אינו מאושר", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "יישום \"%s\" לא ניתן להתקנה כיוון שקובץ appinfo לא ניתן לקריאה.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "לא ניתן להתקין את היישומון „%s” כיוון שאין לו תמיכה בגרסה זו של השרת.", + "__language_name__" : "עברית", + "This is an automatically sent email, please do not reply." : "זו הודעת דוא״ל שנשלחה אוטומטית, נא לא להגיב.", + "Help" : "עזרה", + "Apps" : "יישומים", + "Settings" : "הגדרות", + "Log out" : "התנתק", + "Users" : "משתמשים", + "Unknown user" : "משתמש לא ידוע", + "Additional settings" : "הגדרות נוספות", + "%s enter the database username and name." : "%s יש להכניס את שם המשתמש ושם מסד הנתונים.", + "%s enter the database username." : "%s נכנס למסד נתוני שמות המשתמשים.", + "%s enter the database name." : "%s נכנס למסד נתוני השמות.", + "%s you may not use dots in the database name" : "%s לא ניתן להשתמש בנקודות בשם מסד הנתונים", + "You need to enter details of an existing account." : "עליך להקליד פרטים של חשבון קיים.", + "Oracle connection could not be established" : "לא ניתן היה ליצור חיבור Oracle", + "Oracle username and/or password not valid" : "שם משתמש ו/או סיסמת Oracle אינם תקפים", + "PostgreSQL username and/or password not valid" : "שם משתמש ו/או סיסמת PostgreSQL אינם תקפים", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X אינו נתמך ו- %s לא יעבוד כשורה בפלטפורמה זו. ניתן לקחת סיכון ולהשתמש באחריותך! ", + "For the best results, please consider using a GNU/Linux server instead." : "לתוצאות הכי טובות, יש לשקול שימוש בשרת GNU/Linux במקום.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "נראה ש- %s עובד על בסיס סביבת 32-bit PHP ושה- open_basedir הוגדר בקובץ php.ini. מצב זה יוביל לבעיות עם קבצים הגדולים מ- 4 GB ואינו מומלץ לחלוטין.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "יש להסיר את הגדרת open_basedir מתוך קובץ php.ini או להחליף לסביבת 64-bit PHP.", + "Set an admin username." : "קביעת שם משתמש מנהל", + "Set an admin password." : "קביעת סיסמת מנהל", + "Can't create or write into the data directory %s" : "לא ניתן ליצור או לכתוב לתוך תיקיית הנתונים %s", + "Invalid Federated Cloud ID" : "זיהוי ענן מאוגד לא חוקי", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "צד אחורי לשיתוף %s חייב ליישם את ממשק OCP\\Share_Backend", + "Sharing backend %s not found" : "צד אחורי לשיתוף %s לא נמצא", + "Sharing backend for %s not found" : "צד אחורי לשיתוף של %s לא נמצא", + "»%s« added a note to a file shared with you" : "התווספה הערה על קובץ ששותף את על ידי „%s”", + "Open »%s«" : "פתיחת „%s”", + "%1$s via %2$s" : "%1$s דרך %2$s", + "You are not allowed to share %s" : "אינך רשאי/ת לשתף %s", + "Can’t increase permissions of %s" : "לא ניתן לחזק את ההרשאות של %s", + "Files can’t be shared with delete permissions" : "לא ניתן לשתף קבצים עם הרשאת מחיקה", + "Files can’t be shared with create permissions" : "לא ניתן לשתף קבצים עם הרשאות יצירה", + "Expiration date is in the past" : "תאריך תפוגה הנו בעבר", + "Can’t set expiration date more than %s days in the future" : "לא ניתן להגדיר את תאריך התפוגה מעל %s ימים בעתיד", + "%1$s shared »%2$s« with you" : "%2$s שותף אתך על ידי %1$s", + "%1$s shared »%2$s« with you." : "„%2$s” שותף אתך על ידי %1$s.", + "Click the button below to open it." : "יש ללחוץ על הכפתור להלן כדי לפתוח אותו.", + "The requested share does not exist anymore" : "השיתוף המבוקש אינו קיים עוד", + "Could not find category \"%s\"" : "לא ניתן למצוא את הקטגוריה „%s“", + "Sunday" : "יום ראשון", + "Monday" : "יום שני", + "Tuesday" : "יום שלישי", + "Wednesday" : "יום רביעי", + "Thursday" : "יום חמישי", + "Friday" : "יום שישי", + "Saturday" : "שבת", + "Sun." : "ראשון", + "Mon." : "שני", + "Tue." : "שלישי", + "Wed." : "רביעי", + "Thu." : "חמישי", + "Fri." : "שישי", + "Sat." : "שבת", + "Su" : "א", + "Mo" : "ב", + "Tu" : "ג", + "We" : "ד", + "Th" : "ה", + "Fr" : "ו", + "Sa" : "ש", + "January" : "ינואר", + "February" : "פברואר", + "March" : "מרץ", + "April" : "אפריל", + "May" : "מאי", + "June" : "יוני", + "July" : "יולי", + "August" : "אוגוסט", + "September" : "ספטמבר", + "October" : "אוקטובר", + "November" : "נובמבר", + "December" : "דצמבר", + "Jan." : "ינו׳", + "Feb." : "פבר׳", + "Mar." : "מרץ", + "Apr." : "אפר׳", + "May." : "מאי", + "Jun." : "יונ׳", + "Jul." : "יול׳", + "Aug." : "אוג׳", + "Sep." : "ספט׳", + "Oct." : "אוק׳", + "Nov." : "נוב׳", + "Dec." : "דצמ׳", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "רק התווים הבאים מאושרים לשם משתמש: \"a-z\", \"A-Z\", \"0-9\", וגם \"_.@-'\"", + "A valid username must be provided" : "יש לספק שם משתמש תקני", + "Username contains whitespace at the beginning or at the end" : "שם המשתמש מכיל רווח בתחילתו או בסופו", + "Username must not consist of dots only" : "שם המשתמש לא יכול להיות מורכב מנקודות בלבד", + "A valid password must be provided" : "יש לספק ססמה תקנית", + "The username is already being used" : "השם משתמש כבר בשימוש", + "Could not create user" : "לא ניתן ליצור משתמש", + "User disabled" : "משתמש מנוטרל", + "Login canceled by app" : "התחברות בוטלה על ידי יישום", + "a safe home for all your data" : "בית בטוח עבור כל המידע שלך", + "File is currently busy, please try again later" : "הקובץ בשימוש כרגע, יש לנסות שוב מאוחר יותר", + "Can't read file" : "לא ניתן לקרוא קובץ", + "Application is not enabled" : "יישומים אינם מופעלים", + "Authentication error" : "שגיאת הזדהות", + "Token expired. Please reload page." : "פג תוקף. נא לטעון שוב את הדף.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "לא מותקנים דרייברים למסד הנתונים (sqlite, mysql, או postgresql).", + "Cannot write into \"config\" directory" : "לא ניתן לכתוב לתיקיית \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית ההגדרות. נא לעיין ב־%s", + "Cannot write into \"apps\" directory" : "לא ניתן לכתוב לתיקיית \"apps\"", + "Cannot create \"data\" directory" : "לא ניתן ליצור תיקיית „data”", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית הבסיס. נא לעיין ב־%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "בדרך כלל ניתן לתקן הרשאות על ידי מתן גישה לשרת שלך אל תיקיית העל. נא לעיין ב־%s.", + "Setting locale to %s failed" : "הגדרת שפה ל- %s נכשלה", + "Please install one of these locales on your system and restart your webserver." : "יש להתקין אחת מהשפות על המערכת שלך ולהפעיל מחדש את שרת האינטרנט.", + "PHP module %s not installed." : "מודול PHP %s אינו מותקן.", + "Please ask your server administrator to install the module." : "יש לבקש ממנהל השרת שלך להתקין את המודול.", + "PHP setting \"%s\" is not set to \"%s\"." : "הגדרות PHP \"%s\" אינם מוגדרות ל- \"%s\"", + "Adjusting this setting in php.ini will make Nextcloud run again" : "התאמת ההגדרה הזו ב־php.ini יאפשר ל־Nextcloud לפעול שוב", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload מוגדר ל- \"%s\" במקום הערך המצופה \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "לתיקון בעיה זו יש להגדיר mbstring.func_overload כ- 0 iבקובץ ה- php.ini שלך", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 נדרש לכל הפחות. כרגע %s מותקן.", + "To fix this issue update your libxml2 version and restart your web server." : "לתיקון הבעיה יש לעדכן את גרסת ה- libxml2 שלך ולהפעיל מחדש את שרת האינטרנט שלך.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ככל הנראה מוגדר ל- strip inline doc blocks. זה יגרום למספר יישומי ליבה לא להיות נגישים.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "זה ככל הנראה נגרם על ידי מאיץ/מטמון כמו Zend OPcache או eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "מודולי PHP הותקנו, אך עדיין רשומים כחסרים?", + "Please ask your server administrator to restart the web server." : "יש לבקש ממנהל השרת שלך להפעיל מחדש את שרת האינטרנט.", + "PostgreSQL >= 9 required" : "נדרש PostgreSQL >= 9", + "Please upgrade your database version" : "יש לשדרג את גרסת מסד הנתונים שלך", + "Your data directory is readable by other users" : "תיקיית הנתונים שלך ניתנת לקריאה על ידי משתמשים אחרים", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "יש לשנות את ההרשאות ל- 0770 כך שהתיקייה לא תרשם על ידי משתמשים אחרים.", + "Your data directory must be an absolute path" : "תיקיית הנתונים שלך חייבת להיות במבנה של נתיב מלא (מיקום מוחלט)", + "Check the value of \"datadirectory\" in your configuration" : "יש לבדוק את הערך \"datadirectory\" בהגדרות התצורה שלך", + "Your data directory is invalid" : "תיקיית הנתונים שלך שגויה", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "נא לוודא שיש קובץ בשם „‎.ocdata” בבסיס תיקיית הנתונים שלך.", + "Action \"%s\" not supported or implemented." : "הפעולה „%s” אינה נתמכת או שאינה מוטמעת.", + "Authentication failed, wrong token or provider ID given" : "האימות נכשל, האסימון או מזהה הספק שסופקו שגויים", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "חסרים משתנים להשלמת הבקשה. המשתנים החסרים: „%s”", + "Could not obtain lock type %d on \"%s\"." : "לא ניתן היה להשיג סוג נעילה %d ב- \"%s\".", + "Storage unauthorized. %s" : "אחסון לא מורשה. %s", + "Storage incomplete configuration. %s" : "תצורה לא מושלמת של האחסון. %s", + "Storage connection error. %s" : "שגיאת חיבור אחסון. %s", + "Storage is temporarily not available" : "האחסון אינו זמין כרגע", + "Storage connection timeout. %s" : "פסק זמן חיבור אחסון. %s", + "Following databases are supported: %s" : "מסדי הנתונים הבאים נתמכים: %s", + "Following platforms are supported: %s" : "הפלטפורמות הבאות נתמכות: %s", + "Overview" : "סקירה", + "Basic settings" : "הגדרות בסיסיות", + "Sharing" : "שיתוף", + "Security" : "אבטחה", + "Groupware" : "קבוצתי", + "Personal info" : "פרטים אישיים", + "Mobile & desktop" : "נייד ושולחן עבודה", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "בדרך כלל ניתן לתקן זאת על ידי הענקת גישה לשרת לכתוב לתיקיית היישומונים או להשבית את חנות היישומונים בקובץ ההגדרות. נא לעיין ב־%s" +},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/hr.js b/docker/overlays/nextcloud/html/lib/l10n/hr.js new file mode 100644 index 0000000..234b1a4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hr.js @@ -0,0 +1,238 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Pisanje u direktorij „config” nije moguće!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ovo se obično može ispraviti tako da se web poslužitelju dopusti pristup za pisanje u direktoriju config", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ako želite da datoteku config.php ostane samo za čitanje, postavite opciju „config_is_read_only” na „true”.", + "See %s" : "Pogledajte %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ovo se obično može ispraviti tako da se web-poslužitelju dopusti pristup za pisanje u direktoriju config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ako želite da datoteku config.php ostane samo za čitanje, postavite opciju „config_is_read_only” na „true”. Pogledajte %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke aplikacije %1$s nisu ispravno zamijenjene. Provjerite je li inačica kompatibilna s poslužiteljem.", + "Sample configuration detected" : "Pronađena ogledna konfiguracija", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Otkriveno je kopiranje ogledne konfiguracije. To može dovesti do poteškoća u radu vaše instalacije i nije podržano. Pročitajte dokumentaciju prije nego što izvršite promjene u config.php", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Obrazovno izdanje", + "Enterprise bundle" : "Enterprise paket", + "Groupware bundle" : "Paket grupnog softvera", + "Hub bundle" : "Paket alata", + "Social sharing bundle" : "Paket dijeljenja na društvenim mrežama", + "PHP %s or higher is required." : "PHP inačica treba biti %s ili viša.", + "PHP with a version lower than %s is required." : "Potreban je PHP inačice manje od %s.", + "%sbit or higher PHP required." : "Potreban je %s-bitni ili viši PHP.", + "The following databases are supported: %s" : "Podržane su sljedeće baze podataka: %s", + "The command line tool %s could not be found" : "Alat naredbenog retka %s nije pronađen", + "The library %s is not available." : "Biblioteka %s nije dostupna.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Potrebna je biblioteka %1$s inačice veće od %2$s – dostupna je inačica %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Potrebna je biblioteka %1$s inačice manje od %2$s – dostupna je inačica %3$s.", + "The following platforms are supported: %s" : "Podržane su sljedeće platforme: %s", + "Server version %s or higher is required." : "Inačica poslužitelja treba biti %s ili viša.", + "Server version %s or lower is required." : "Potrebna je inačica poslužitelja %s ili niža.", + "Logged in user must be an admin or sub admin" : "Prijavljeni korisnik mora biti administrator ili podadministrator", + "Logged in user must be an admin" : "Korisnik koji je prijavljen mora biti administrator", + "Wiping of device %s has started" : "Počelo je brisanje sadržaja s uređaja %s", + "Wiping of device »%s« has started" : "Počelo je brisanje sadržaja s uređaja »%s«", + "»%s« started remote wipe" : "»%s« je počeo daljinsko brisanje", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Uređaj ili aplikacija »%s« započela je postupak daljinskog brisanja. Primit ćete još jednu poruku e-pošte nakon završetka postupka", + "Wiping of device %s has finished" : "Završilo je brisanje sadržaja s uređaja %s", + "Wiping of device »%s« has finished" : "Završilo je brisanje sadržaja s uređaja »%s«", + "»%s« finished remote wipe" : "»%s« je završio daljinsko brisanje", + "Device or application »%s« has finished the remote wipe process." : "Uređaj ili aplikacija »%s« završila je postupak daljinskog brisanja.", + "Remote wipe started" : "Pokrenuto je udaljeno brisanje", + "A remote wipe was started on device %s" : "Pokrenuto je udaljeno brisanje na uređaju %s", + "Remote wipe finished" : "Udaljeno brisanje je završeno", + "The remote wipe on %s has finished" : "Udaljeno brisanje %s je završeno", + "Authentication" : "Autentifikacija", + "Unknown filetype" : "Vrsta datoteke nepoznata", + "Invalid image" : "Slika neispravna", + "Avatar image is not square" : "Slika avatara nije kvadratna", + "today" : "danas", + "tomorrow" : "sutra", + "yesterday" : "jučer", + "_in %n day_::_in %n days_" : ["za %n dan","za %n dana","za %n dana"], + "_%n day ago_::_%n days ago_" : ["Prije %n dana","Prije %n dana","Prije %n dana"], + "next month" : "sljedeći mjesec", + "last month" : "prošli mjesec", + "_in %n month_::_in %n months_" : ["za %n mjesec","za %n mjeseci","za %n mjeseci"], + "_%n month ago_::_%n months ago_" : ["prije %n mjeseci","prije %n mjeseci","prije %n mjeseci"], + "next year" : "sljedeće godine", + "last year" : "prošle godine", + "_in %n year_::_in %n years_" : ["za %n godinu","za %n godina","za %n godina"], + "_%n year ago_::_%n years ago_" : ["Prije %n godine","Prije %n godina","Prije %n godina"], + "_in %n hour_::_in %n hours_" : ["za %n sat","za %n sati","za %n sati"], + "_%n hour ago_::_%n hours ago_" : ["Prije %n sata","Prije %n sati","Prije %n sati"], + "_in %n minute_::_in %n minutes_" : ["za %n minutu","za %n minuta","za %n minuta"], + "_%n minute ago_::_%n minutes ago_" : ["Prije %n minute","Prije %n minuta","Prije %n minuta"], + "in a few seconds" : "za nekoliko sekundi", + "seconds ago" : "prije nekoliko sekundi", + "Empty file" : "Prazna datoteka", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s ID-om: %s ne postoji. Omogućite ga u postavkama svojih aplikacija ili se obratite administratoru.", + "File name is a reserved word" : "Naziv datoteke je rezervirana riječ", + "File name contains at least one invalid character" : "Naziv datoteke sadrži barem jedan nevažeći znak", + "File name is too long" : "Naziv datoteke je predugačak", + "Dot files are not allowed" : "Datoteke s točkama nisu dopuštene", + "Empty filename is not allowed" : "Datoteke bez naziva nisu dopuštene", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikaciju „%s” nije moguće instalirati jer se ne može pročitati datoteka s podacima o aplikaciji.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikaciju „%s” nije moguće instalirati jer nije kompatibilna s ovom inačicom poslužitelja.", + "__language_name__" : "__language_name__", + "This is an automatically sent email, please do not reply." : "Ovo je automatski poslana poruka e-pošte, nemojte odgovarati.", + "Help" : "Pomoć", + "Apps" : "Aplikacije", + "Settings" : "Postavke", + "Log out" : "Odjava", + "Users" : "Korisnici", + "Unknown user" : "Nepoznat korisnik", + "Additional settings" : "Dodatne postavke", + "%s enter the database username and name." : "%s unesite korisničko ime i naziv baze podataka.", + "%s enter the database username." : "%s unesite korisničko ime baze podataka.", + "%s enter the database name." : "%s unesite naziv baze podataka.", + "%s you may not use dots in the database name" : "%s ne smijete koristiti točke u nazivu baze podataka", + "MySQL username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka MySQL", + "You need to enter details of an existing account." : "Trebate unijeti informacije o postojećem računu.", + "Oracle connection could not be established" : "Nije moguće uspostaviti vezu s bazom podataka Oracle", + "Oracle username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka Oracle", + "PostgreSQL username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nije podržan i %s na ovoj platformi neće raditi kako treba. Koristite na vlastiti rizik!", + "For the best results, please consider using a GNU/Linux server instead." : "Za najbolje rezultate razmotrite mogućnost korištenja poslužitelja GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Čini se da se ova %s instanca izvodi u 32-bitnom PHP okruženju, a open_basedir je konfiguriran u php.ini. To će dovesti do problema s datotekama većim od 4 GB i stoga se ne preporučuje.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Uklonite postavku open_basedir iz datoteke php.ini ili se prebacite na 64-bitni PHP.", + "Set an admin username." : "Postavite korisničko ime administratora.", + "Set an admin password." : "Postavite zaporku administratora.", + "Can't create or write into the data directory %s" : "Nije moguće stvoriti ili pisati u direktorij s podacima %s", + "Invalid Federated Cloud ID" : "Nevažeći ID udruženog oblaka", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Pozadina za dijeljenje %s mora implementirati sučelje OCP\\Share_Backend", + "Sharing backend %s not found" : "Pozadina za dijeljenje %s nije pronađena", + "Sharing backend for %s not found" : "Pozadina za dijeljenje za %s nije pronađena", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s dijeli »%2$s« s vama i želi dodati:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s dijeli »%2$s« s vama i želi dodati", + "»%s« added a note to a file shared with you" : "»%s« je dodao bilješku datoteci koju dijeli s vama", + "Open »%s«" : "Otvori »%s«", + "%1$s via %2$s" : "%1$s putem %2$s", + "You are not allowed to share %s" : "Nije vam dopušteno dijeliti %s", + "Can’t increase permissions of %s" : "Nije moguće povećati dopuštenja za %s", + "Files can’t be shared with delete permissions" : "Datoteke se ne mogu dijeliti s dopuštenjima za brisanje", + "Files can’t be shared with create permissions" : "Datoteke se ne mogu dijeliti s dopuštenjima za stvaranje", + "Expiration date is in the past" : "Datum isteka je u prošlosti", + "Can’t set expiration date more than %s days in the future" : "Ne može se postaviti datum isteka više od %s dana u budućnosti", + "%1$s shared »%2$s« with you" : "%1$s dijeli »%2$s« s vama", + "%1$s shared »%2$s« with you." : "%1$s dijeli »%2$s« s vama.", + "Click the button below to open it." : "Kliknite gumb u nastavku za otvaranje.", + "The requested share does not exist anymore" : "Zatraženo dijeljenje više ne postoji", + "Could not find category \"%s\"" : "Kategorija „%s” nije pronađena", + "Sunday" : "Nedjelja", + "Monday" : "Ponedjeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Srijeda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Uto.", + "Wed." : "Sri.", + "Thu." : "Čet.", + "Fri." : "Pet.", + "Sat." : "Sub.", + "Su" : "Ne.", + "Mo" : "Po.", + "Tu" : "Ut.", + "We" : "Sr.", + "Th" : "Če.", + "Fr" : "Pe.", + "Sa" : "Su.", + "January" : "Siječanj", + "February" : "Veljača", + "March" : "Ožujak", + "April" : "Travanj", + "May" : "Svibanj", + "June" : "Lipanj", + "July" : "Srpanj", + "August" : "Kolovoz", + "September" : "Rujan", + "October" : "Listopad", + "November" : "Studeni", + "December" : "Prosinac", + "Jan." : "Sij.", + "Feb." : "Velj.", + "Mar." : "Ožu.", + "Apr." : "Tra.", + "May." : "Svi.", + "Jun." : "Lip.", + "Jul." : "Srp.", + "Aug." : "Kol.", + "Sep." : "Ruj.", + "Oct." : "Lis.", + "Nov." : "Stu.", + "Dec." : "Pro.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "U korisničkom imenu dopušteni su samo sljedeći znakovi: „a – z”, „A – Z”, „0 – 9” i „_.@-'”", + "A valid username must be provided" : "Nužno je navesti ispravno korisničko ime", + "Username contains whitespace at the beginning or at the end" : "Korisničko ime sadrži bijeli prostor na početku ili na kraju", + "Username must not consist of dots only" : "Korisničko ime ne smije se sastojati samo od točkica", + "Username is invalid because files already exist for this user" : "Korisničko ime je nevažeće jer već postoje datoteke za ovog korisnika", + "A valid password must be provided" : "Nužno je navesti ispravnu zaporku", + "The username is already being used" : "Korisničko ime se već koristi", + "Could not create user" : "Nije moguće stvoriti korisnika", + "User disabled" : "Korisnik je onemogućen", + "Login canceled by app" : "Prijava je otkazana putem aplikacije", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikaciju „%1$s” nije moguće instalirati jer nisu ispunjene sljedeće ovisnosti: %2$s", + "a safe home for all your data" : "siguran dom za sve vaše podatke", + "File is currently busy, please try again later" : "Datoteka je trenutno zauzeta, pokušajte ponovo kasnije", + "Can't read file" : "Datoteka se ne može pročitati", + "Application is not enabled" : "Aplikacija nije omogućena", + "Authentication error" : "Pogrešna autentifikacija", + "Token expired. Please reload page." : "Token je istekao. Ponovno učitajte stranicu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nisu instalirani upravljački programi baze podataka (sqlite, mysql ili postgresql).", + "Cannot write into \"config\" directory" : "Pisanje u direktorij „config” nije moguće", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u konfiguracijski direktorij. Pogledajte %s", + "Cannot write into \"apps\" directory" : "Nije moguće pisati u direktorij „apps”", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ovo se obično može popraviti tako da se web-poslužitelju dopusti pristup za pisanje u aplikacijski direktorij ili onemogućivanjem trgovine aplikacijama (App Store) u konfiguracijskoj datoteci.", + "Cannot create \"data\" directory" : "Nije moguće stvoriti direktorij „data”", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u korijenski direktorij. Pogledajte %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Dopuštenja se obično mogu popraviti tako da se web poslužitelju dopusti pristup za pisanje u korijenski direktorij. Pogledajte %s.", + "Setting locale to %s failed" : "Neuspješno postavljanje regionalne sheme na %s", + "Please install one of these locales on your system and restart your webserver." : "Instalirajte jednu od ovih regionalnih shema u svoj sustav i ponovno pokrenite svoj web poslužitelj.", + "PHP module %s not installed." : "PHP modul %s nije instaliran.", + "Please ask your server administrator to install the module." : "Zamolite svog administratora poslužitelja da instalira modul.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP postavka „%s” nije postavljena na „%s”.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Podešavanjem ove postavke u php.ini možete ponovno pokrenuti Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je postavljen na „%s” umjesto očekivane vrijednosti „0”", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Kako biste riješili ovaj problem, postavite mbstring.func_overload na 0 u datoteci php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Potreban je barem Libxml2 2.7.0. Trenutno je instalirana inačica %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Kako biste riješili ovaj problem, ažurirajte svoju inačicu libxml2 i ponovno pokrenite web poslužitelj.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je očigledno postavljen za uklanjanje inline doc blokova. Zbog toga će nekoliko osnovnih aplikacija biti nedostupne.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Uzrok tome je vjerojatno neki ubrzivač predmemoriranja kao što je Zend OPcache ili eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduli su instalirani, ali još uvijek su na popisu onih koji nedostaju?", + "Please ask your server administrator to restart the web server." : "Molimo zamolite svog administratora poslužitelja da ponovno pokrene web poslužitelj.", + "PostgreSQL >= 9 required" : "Potreban je PostgreSQL >= 9", + "Please upgrade your database version" : "Ažurirajte svoju inačicu baze podataka", + "Your data directory is readable by other users" : "Vaš podatkovni direktorij mogu čitati drugi korisnici", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Promijenite dozvole na 0770 tako da se tim direktorijem ne mogu služiti drugi korisnici.", + "Your data directory must be an absolute path" : "Vaš podatkovni direktorij mora imati apsolutni put", + "Check the value of \"datadirectory\" in your configuration" : "Provjerite vrijednost „datadirectory” u svojoj konfiguraciji", + "Your data directory is invalid" : "Vaš je podatkovni direktorij nevažeći", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Provjerite postoji li datoteka pod nazivom „.ocdata” u korijenu podatkovnog direktorija.", + "Action \"%s\" not supported or implemented." : "Radnja „%s” nije podržana ili provedena.", + "Authentication failed, wrong token or provider ID given" : "Autentifikacija nije uspjela, dan je pogrešan token ili ID davatelja usluge", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Nedostaju parametri za ispunjavanje zahtjeva. Nedostaju parametri: „%s”", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID „%1$s” već koristi davatelj usluga udruženog oblaka „%2$s”", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Davatelj usluga udruženog oblaka s ID-om: „%s” ne postoji.", + "Could not obtain lock type %d on \"%s\"." : "Nije moguće dobiti vrstu zaključavanja %d na „%s”.", + "Storage unauthorized. %s" : "Neovlaštena pohrana. %s", + "Storage incomplete configuration. %s" : "Nepotpuna konfiguracija pohrane. %s", + "Storage connection error. %s" : "Pogreška veze pohrane. %s", + "Storage is temporarily not available" : "Pohrana privremeno nije dostupna", + "Storage connection timeout. %s" : "Istek veze pohrane. %s", + "Following databases are supported: %s" : "Podržane su sljedeće baze podataka: %s", + "Following platforms are supported: %s" : "Podržane su sljedeće platforme: %s", + "Overview" : "Pregled", + "Basic settings" : "Osnovne postavke", + "Sharing" : "Dijeljenje", + "Security" : "Sigurnost", + "Groupware" : "Groupware", + "Personal info" : "Osobne informacije", + "Mobile & desktop" : "Mobilni uređaji i osobna računala", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u aplikacijski direktorij ili onemogućivanjem appstorea u konfiguracijskoj datoteci. Pogledajte %s" +}, +"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/hr.json b/docker/overlays/nextcloud/html/lib/l10n/hr.json new file mode 100644 index 0000000..68a2c1a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hr.json @@ -0,0 +1,236 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Pisanje u direktorij „config” nije moguće!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ovo se obično može ispraviti tako da se web poslužitelju dopusti pristup za pisanje u direktoriju config", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ako želite da datoteku config.php ostane samo za čitanje, postavite opciju „config_is_read_only” na „true”.", + "See %s" : "Pogledajte %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ovo se obično može ispraviti tako da se web-poslužitelju dopusti pristup za pisanje u direktoriju config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ako želite da datoteku config.php ostane samo za čitanje, postavite opciju „config_is_read_only” na „true”. Pogledajte %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke aplikacije %1$s nisu ispravno zamijenjene. Provjerite je li inačica kompatibilna s poslužiteljem.", + "Sample configuration detected" : "Pronađena ogledna konfiguracija", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Otkriveno je kopiranje ogledne konfiguracije. To može dovesti do poteškoća u radu vaše instalacije i nije podržano. Pročitajte dokumentaciju prije nego što izvršite promjene u config.php", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Obrazovno izdanje", + "Enterprise bundle" : "Enterprise paket", + "Groupware bundle" : "Paket grupnog softvera", + "Hub bundle" : "Paket alata", + "Social sharing bundle" : "Paket dijeljenja na društvenim mrežama", + "PHP %s or higher is required." : "PHP inačica treba biti %s ili viša.", + "PHP with a version lower than %s is required." : "Potreban je PHP inačice manje od %s.", + "%sbit or higher PHP required." : "Potreban je %s-bitni ili viši PHP.", + "The following databases are supported: %s" : "Podržane su sljedeće baze podataka: %s", + "The command line tool %s could not be found" : "Alat naredbenog retka %s nije pronađen", + "The library %s is not available." : "Biblioteka %s nije dostupna.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Potrebna je biblioteka %1$s inačice veće od %2$s – dostupna je inačica %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Potrebna je biblioteka %1$s inačice manje od %2$s – dostupna je inačica %3$s.", + "The following platforms are supported: %s" : "Podržane su sljedeće platforme: %s", + "Server version %s or higher is required." : "Inačica poslužitelja treba biti %s ili viša.", + "Server version %s or lower is required." : "Potrebna je inačica poslužitelja %s ili niža.", + "Logged in user must be an admin or sub admin" : "Prijavljeni korisnik mora biti administrator ili podadministrator", + "Logged in user must be an admin" : "Korisnik koji je prijavljen mora biti administrator", + "Wiping of device %s has started" : "Počelo je brisanje sadržaja s uređaja %s", + "Wiping of device »%s« has started" : "Počelo je brisanje sadržaja s uređaja »%s«", + "»%s« started remote wipe" : "»%s« je počeo daljinsko brisanje", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Uređaj ili aplikacija »%s« započela je postupak daljinskog brisanja. Primit ćete još jednu poruku e-pošte nakon završetka postupka", + "Wiping of device %s has finished" : "Završilo je brisanje sadržaja s uređaja %s", + "Wiping of device »%s« has finished" : "Završilo je brisanje sadržaja s uređaja »%s«", + "»%s« finished remote wipe" : "»%s« je završio daljinsko brisanje", + "Device or application »%s« has finished the remote wipe process." : "Uređaj ili aplikacija »%s« završila je postupak daljinskog brisanja.", + "Remote wipe started" : "Pokrenuto je udaljeno brisanje", + "A remote wipe was started on device %s" : "Pokrenuto je udaljeno brisanje na uređaju %s", + "Remote wipe finished" : "Udaljeno brisanje je završeno", + "The remote wipe on %s has finished" : "Udaljeno brisanje %s je završeno", + "Authentication" : "Autentifikacija", + "Unknown filetype" : "Vrsta datoteke nepoznata", + "Invalid image" : "Slika neispravna", + "Avatar image is not square" : "Slika avatara nije kvadratna", + "today" : "danas", + "tomorrow" : "sutra", + "yesterday" : "jučer", + "_in %n day_::_in %n days_" : ["za %n dan","za %n dana","za %n dana"], + "_%n day ago_::_%n days ago_" : ["Prije %n dana","Prije %n dana","Prije %n dana"], + "next month" : "sljedeći mjesec", + "last month" : "prošli mjesec", + "_in %n month_::_in %n months_" : ["za %n mjesec","za %n mjeseci","za %n mjeseci"], + "_%n month ago_::_%n months ago_" : ["prije %n mjeseci","prije %n mjeseci","prije %n mjeseci"], + "next year" : "sljedeće godine", + "last year" : "prošle godine", + "_in %n year_::_in %n years_" : ["za %n godinu","za %n godina","za %n godina"], + "_%n year ago_::_%n years ago_" : ["Prije %n godine","Prije %n godina","Prije %n godina"], + "_in %n hour_::_in %n hours_" : ["za %n sat","za %n sati","za %n sati"], + "_%n hour ago_::_%n hours ago_" : ["Prije %n sata","Prije %n sati","Prije %n sati"], + "_in %n minute_::_in %n minutes_" : ["za %n minutu","za %n minuta","za %n minuta"], + "_%n minute ago_::_%n minutes ago_" : ["Prije %n minute","Prije %n minuta","Prije %n minuta"], + "in a few seconds" : "za nekoliko sekundi", + "seconds ago" : "prije nekoliko sekundi", + "Empty file" : "Prazna datoteka", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s ID-om: %s ne postoji. Omogućite ga u postavkama svojih aplikacija ili se obratite administratoru.", + "File name is a reserved word" : "Naziv datoteke je rezervirana riječ", + "File name contains at least one invalid character" : "Naziv datoteke sadrži barem jedan nevažeći znak", + "File name is too long" : "Naziv datoteke je predugačak", + "Dot files are not allowed" : "Datoteke s točkama nisu dopuštene", + "Empty filename is not allowed" : "Datoteke bez naziva nisu dopuštene", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikaciju „%s” nije moguće instalirati jer se ne može pročitati datoteka s podacima o aplikaciji.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikaciju „%s” nije moguće instalirati jer nije kompatibilna s ovom inačicom poslužitelja.", + "__language_name__" : "__language_name__", + "This is an automatically sent email, please do not reply." : "Ovo je automatski poslana poruka e-pošte, nemojte odgovarati.", + "Help" : "Pomoć", + "Apps" : "Aplikacije", + "Settings" : "Postavke", + "Log out" : "Odjava", + "Users" : "Korisnici", + "Unknown user" : "Nepoznat korisnik", + "Additional settings" : "Dodatne postavke", + "%s enter the database username and name." : "%s unesite korisničko ime i naziv baze podataka.", + "%s enter the database username." : "%s unesite korisničko ime baze podataka.", + "%s enter the database name." : "%s unesite naziv baze podataka.", + "%s you may not use dots in the database name" : "%s ne smijete koristiti točke u nazivu baze podataka", + "MySQL username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka MySQL", + "You need to enter details of an existing account." : "Trebate unijeti informacije o postojećem računu.", + "Oracle connection could not be established" : "Nije moguće uspostaviti vezu s bazom podataka Oracle", + "Oracle username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka Oracle", + "PostgreSQL username and/or password not valid" : "Neispravno korisničko ime i/ili zaporka baze podataka PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nije podržan i %s na ovoj platformi neće raditi kako treba. Koristite na vlastiti rizik!", + "For the best results, please consider using a GNU/Linux server instead." : "Za najbolje rezultate razmotrite mogućnost korištenja poslužitelja GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Čini se da se ova %s instanca izvodi u 32-bitnom PHP okruženju, a open_basedir je konfiguriran u php.ini. To će dovesti do problema s datotekama većim od 4 GB i stoga se ne preporučuje.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Uklonite postavku open_basedir iz datoteke php.ini ili se prebacite na 64-bitni PHP.", + "Set an admin username." : "Postavite korisničko ime administratora.", + "Set an admin password." : "Postavite zaporku administratora.", + "Can't create or write into the data directory %s" : "Nije moguće stvoriti ili pisati u direktorij s podacima %s", + "Invalid Federated Cloud ID" : "Nevažeći ID udruženog oblaka", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Pozadina za dijeljenje %s mora implementirati sučelje OCP\\Share_Backend", + "Sharing backend %s not found" : "Pozadina za dijeljenje %s nije pronađena", + "Sharing backend for %s not found" : "Pozadina za dijeljenje za %s nije pronađena", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s dijeli »%2$s« s vama i želi dodati:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s dijeli »%2$s« s vama i želi dodati", + "»%s« added a note to a file shared with you" : "»%s« je dodao bilješku datoteci koju dijeli s vama", + "Open »%s«" : "Otvori »%s«", + "%1$s via %2$s" : "%1$s putem %2$s", + "You are not allowed to share %s" : "Nije vam dopušteno dijeliti %s", + "Can’t increase permissions of %s" : "Nije moguće povećati dopuštenja za %s", + "Files can’t be shared with delete permissions" : "Datoteke se ne mogu dijeliti s dopuštenjima za brisanje", + "Files can’t be shared with create permissions" : "Datoteke se ne mogu dijeliti s dopuštenjima za stvaranje", + "Expiration date is in the past" : "Datum isteka je u prošlosti", + "Can’t set expiration date more than %s days in the future" : "Ne može se postaviti datum isteka više od %s dana u budućnosti", + "%1$s shared »%2$s« with you" : "%1$s dijeli »%2$s« s vama", + "%1$s shared »%2$s« with you." : "%1$s dijeli »%2$s« s vama.", + "Click the button below to open it." : "Kliknite gumb u nastavku za otvaranje.", + "The requested share does not exist anymore" : "Zatraženo dijeljenje više ne postoji", + "Could not find category \"%s\"" : "Kategorija „%s” nije pronađena", + "Sunday" : "Nedjelja", + "Monday" : "Ponedjeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Srijeda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Uto.", + "Wed." : "Sri.", + "Thu." : "Čet.", + "Fri." : "Pet.", + "Sat." : "Sub.", + "Su" : "Ne.", + "Mo" : "Po.", + "Tu" : "Ut.", + "We" : "Sr.", + "Th" : "Če.", + "Fr" : "Pe.", + "Sa" : "Su.", + "January" : "Siječanj", + "February" : "Veljača", + "March" : "Ožujak", + "April" : "Travanj", + "May" : "Svibanj", + "June" : "Lipanj", + "July" : "Srpanj", + "August" : "Kolovoz", + "September" : "Rujan", + "October" : "Listopad", + "November" : "Studeni", + "December" : "Prosinac", + "Jan." : "Sij.", + "Feb." : "Velj.", + "Mar." : "Ožu.", + "Apr." : "Tra.", + "May." : "Svi.", + "Jun." : "Lip.", + "Jul." : "Srp.", + "Aug." : "Kol.", + "Sep." : "Ruj.", + "Oct." : "Lis.", + "Nov." : "Stu.", + "Dec." : "Pro.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "U korisničkom imenu dopušteni su samo sljedeći znakovi: „a – z”, „A – Z”, „0 – 9” i „_.@-'”", + "A valid username must be provided" : "Nužno je navesti ispravno korisničko ime", + "Username contains whitespace at the beginning or at the end" : "Korisničko ime sadrži bijeli prostor na početku ili na kraju", + "Username must not consist of dots only" : "Korisničko ime ne smije se sastojati samo od točkica", + "Username is invalid because files already exist for this user" : "Korisničko ime je nevažeće jer već postoje datoteke za ovog korisnika", + "A valid password must be provided" : "Nužno je navesti ispravnu zaporku", + "The username is already being used" : "Korisničko ime se već koristi", + "Could not create user" : "Nije moguće stvoriti korisnika", + "User disabled" : "Korisnik je onemogućen", + "Login canceled by app" : "Prijava je otkazana putem aplikacije", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikaciju „%1$s” nije moguće instalirati jer nisu ispunjene sljedeće ovisnosti: %2$s", + "a safe home for all your data" : "siguran dom za sve vaše podatke", + "File is currently busy, please try again later" : "Datoteka je trenutno zauzeta, pokušajte ponovo kasnije", + "Can't read file" : "Datoteka se ne može pročitati", + "Application is not enabled" : "Aplikacija nije omogućena", + "Authentication error" : "Pogrešna autentifikacija", + "Token expired. Please reload page." : "Token je istekao. Ponovno učitajte stranicu.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nisu instalirani upravljački programi baze podataka (sqlite, mysql ili postgresql).", + "Cannot write into \"config\" directory" : "Pisanje u direktorij „config” nije moguće", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u konfiguracijski direktorij. Pogledajte %s", + "Cannot write into \"apps\" directory" : "Nije moguće pisati u direktorij „apps”", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ovo se obično može popraviti tako da se web-poslužitelju dopusti pristup za pisanje u aplikacijski direktorij ili onemogućivanjem trgovine aplikacijama (App Store) u konfiguracijskoj datoteci.", + "Cannot create \"data\" directory" : "Nije moguće stvoriti direktorij „data”", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u korijenski direktorij. Pogledajte %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Dopuštenja se obično mogu popraviti tako da se web poslužitelju dopusti pristup za pisanje u korijenski direktorij. Pogledajte %s.", + "Setting locale to %s failed" : "Neuspješno postavljanje regionalne sheme na %s", + "Please install one of these locales on your system and restart your webserver." : "Instalirajte jednu od ovih regionalnih shema u svoj sustav i ponovno pokrenite svoj web poslužitelj.", + "PHP module %s not installed." : "PHP modul %s nije instaliran.", + "Please ask your server administrator to install the module." : "Zamolite svog administratora poslužitelja da instalira modul.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP postavka „%s” nije postavljena na „%s”.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Podešavanjem ove postavke u php.ini možete ponovno pokrenuti Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je postavljen na „%s” umjesto očekivane vrijednosti „0”", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Kako biste riješili ovaj problem, postavite mbstring.func_overload na 0 u datoteci php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Potreban je barem Libxml2 2.7.0. Trenutno je instalirana inačica %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Kako biste riješili ovaj problem, ažurirajte svoju inačicu libxml2 i ponovno pokrenite web poslužitelj.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je očigledno postavljen za uklanjanje inline doc blokova. Zbog toga će nekoliko osnovnih aplikacija biti nedostupne.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Uzrok tome je vjerojatno neki ubrzivač predmemoriranja kao što je Zend OPcache ili eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduli su instalirani, ali još uvijek su na popisu onih koji nedostaju?", + "Please ask your server administrator to restart the web server." : "Molimo zamolite svog administratora poslužitelja da ponovno pokrene web poslužitelj.", + "PostgreSQL >= 9 required" : "Potreban je PostgreSQL >= 9", + "Please upgrade your database version" : "Ažurirajte svoju inačicu baze podataka", + "Your data directory is readable by other users" : "Vaš podatkovni direktorij mogu čitati drugi korisnici", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Promijenite dozvole na 0770 tako da se tim direktorijem ne mogu služiti drugi korisnici.", + "Your data directory must be an absolute path" : "Vaš podatkovni direktorij mora imati apsolutni put", + "Check the value of \"datadirectory\" in your configuration" : "Provjerite vrijednost „datadirectory” u svojoj konfiguraciji", + "Your data directory is invalid" : "Vaš je podatkovni direktorij nevažeći", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Provjerite postoji li datoteka pod nazivom „.ocdata” u korijenu podatkovnog direktorija.", + "Action \"%s\" not supported or implemented." : "Radnja „%s” nije podržana ili provedena.", + "Authentication failed, wrong token or provider ID given" : "Autentifikacija nije uspjela, dan je pogrešan token ili ID davatelja usluge", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Nedostaju parametri za ispunjavanje zahtjeva. Nedostaju parametri: „%s”", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID „%1$s” već koristi davatelj usluga udruženog oblaka „%2$s”", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Davatelj usluga udruženog oblaka s ID-om: „%s” ne postoji.", + "Could not obtain lock type %d on \"%s\"." : "Nije moguće dobiti vrstu zaključavanja %d na „%s”.", + "Storage unauthorized. %s" : "Neovlaštena pohrana. %s", + "Storage incomplete configuration. %s" : "Nepotpuna konfiguracija pohrane. %s", + "Storage connection error. %s" : "Pogreška veze pohrane. %s", + "Storage is temporarily not available" : "Pohrana privremeno nije dostupna", + "Storage connection timeout. %s" : "Istek veze pohrane. %s", + "Following databases are supported: %s" : "Podržane su sljedeće baze podataka: %s", + "Following platforms are supported: %s" : "Podržane su sljedeće platforme: %s", + "Overview" : "Pregled", + "Basic settings" : "Osnovne postavke", + "Sharing" : "Dijeljenje", + "Security" : "Sigurnost", + "Groupware" : "Groupware", + "Personal info" : "Osobne informacije", + "Mobile & desktop" : "Mobilni uređaji i osobna računala", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ovo se obično može popraviti tako da se web poslužitelju dopusti pristup za pisanje u aplikacijski direktorij ili onemogućivanjem appstorea u konfiguracijskoj datoteci. Pogledajte %s" +},"pluralForm" :"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/hu.js b/docker/overlays/nextcloud/html/lib/l10n/hu.js new file mode 100644 index 0000000..5c808c5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hu.js @@ -0,0 +1,216 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nem írható a \"config\" könyvtár!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ez rendszerint úgy oldható meg, hogy írási jogot adunk a webszervernek a config könyvtárra.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Avagy, ha jobbnak tűnik tarthatod a config.php fájlt olvashatónak, csak engedélyezd a \"config_is_read_only\" kapcsolót.", + "See %s" : "Lásd %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Avagy, ha jobbnak tűnik tarthatod a config.php fájlt olvashatónak, csak engedélyezd a \"config_is_read_only\" kapcsolót. Továbbiak itt:%s", + "Sample configuration detected" : "A példabeállítások vannak beállítva", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Úgy tűnik a példakonfigurációt próbálja ténylegesen használni. Ez nem támogatott, és működésképtelenné teheti a telepítést. Kérlek olvasd el a dokumentációt és azt követően változtas a config.php-n!", + "%1$s and %2$s" : "%1$s és %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s és %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s és %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s és %5$s", + "Education Edition" : "Oktatási verzió", + "Enterprise bundle" : "Vállalati csomag", + "Groupware bundle" : "Csoportmunka csomag", + "Social sharing bundle" : "Közösségi megosztás csomag", + "PHP %s or higher is required." : "PHP %s vagy ennél újabb szükséges.", + "PHP with a version lower than %s is required." : "Ennél régebbi PHP szükséges: %s.", + "%sbit or higher PHP required." : "%sbites vagy újabb PHP szükséges.", + "The command line tool %s could not be found" : "A parancssori eszköz nem található: %s", + "The library %s is not available." : "A könyvtár %s nem áll rendelkezésre.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s könyvtár %2$s vagy újabb verziója szükséges - elérhető verzió: %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s könyvtár %2$s vagy régebbi verziója szükséges - elérhető verzió: %3$s.", + "Server version %s or higher is required." : "%s vagy újabb szerver verzió szükséges.", + "Server version %s or lower is required." : "%s vagy régebbi szerver verzió szükséges.", + "Logged in user must be an admin" : "A bejelentkezett felhasználónak rendszergazdának kell lennie", + "Authentication" : "Hitelesítés", + "Unknown filetype" : "Ismeretlen fájl típus", + "Invalid image" : "Hibás kép", + "Avatar image is not square" : "Az avatár kép nem négyzetes.", + "today" : "ma", + "tomorrow" : "holnap", + "yesterday" : "tegnap", + "_in %n day_::_in %n days_" : ["%n napon belül","%n napon belül"], + "_%n day ago_::_%n days ago_" : ["%n napja","%n napja"], + "next month" : "következő hónap", + "last month" : "múlt hónapban", + "_in %n month_::_in %n months_" : ["%n hónapon belül","%n hónapon belül"], + "_%n month ago_::_%n months ago_" : ["%n hónapja","%n hónapja"], + "next year" : "következő évben", + "last year" : "tavaly", + "_in %n year_::_in %n years_" : ["%n éven belül","%n éven belül"], + "_%n year ago_::_%n years ago_" : ["%n éve","%n éve"], + "_in %n hour_::_in %n hours_" : ["%n órán belül","%n órán belül"], + "_%n hour ago_::_%n hours ago_" : ["%n órája","%n órája"], + "_in %n minute_::_in %n minutes_" : ["%n percen belül","%n percen belül"], + "_%n minute ago_::_%n minutes ago_" : ["%n perce","%n perce"], + "in a few seconds" : "pár másodpercen belül", + "seconds ago" : "pár másodperce", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "A(z) %s azonosítójú modul nem létezik. Kérlek engedélyezd az app beállításaidban, vagy lépj kapcsolatba a rendszergazdával.", + "File name is a reserved word" : "A fajl neve egy rezervált szó", + "File name contains at least one invalid character" : "A fájlnév legalább egy érvénytelen karaktert tartalmaz!", + "File name is too long" : "A fájlnév túl hosszú!", + "Dot files are not allowed" : "Pontozott fájlok nem engedétlyezettek", + "Empty filename is not allowed" : "Üres fájlnév nem engedétlyezett", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "\"%s\" alkalmazás nem lehet telepíteni, mert az appinfo fájl nem olvasható.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" alkalmazás nem lehet telepíteni, mert nem kompatibilis a szerver jelen verziójával.", + "__language_name__" : "Magyar", + "This is an automatically sent email, please do not reply." : "Ez egy automatikusan küldött levél, kérlek ne válaszolj rá.", + "Help" : "Súgó", + "Apps" : "Alkalmazások", + "Settings" : "Beállítások", + "Log out" : "Kijelentkezés", + "Users" : "Felhasználók", + "Unknown user" : "Ismeretlen felhasználó", + "Additional settings" : "További beállítások", + "%s enter the database username and name." : "%s add meg az adatbázis nevét és felhasználónevét", + "%s enter the database username." : "%s adja meg az adatbázist elérő felhasználó login nevét.", + "%s enter the database name." : "%s adja meg az adatbázis nevét.", + "%s you may not use dots in the database name" : "%s az adatbázis neve nem tartalmazhat pontot", + "You need to enter details of an existing account." : "Egy már létező fiók adatait kell megadnod.", + "Oracle connection could not be established" : "Az Oracle kapcsolat nem hozható létre", + "Oracle username and/or password not valid" : "Az Oracle felhasználói név és/vagy jelszó érvénytelen", + "PostgreSQL username and/or password not valid" : "A PostgreSQL felhasználói név és/vagy jelszó érvénytelen", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "A Mac OS X nem támogatott és %s nem lesz teljesen működőképes. Csak saját felelősségre használja!", + "For the best results, please consider using a GNU/Linux server instead." : "A legjobb eredmény érdekében érdemes GNU/Linux-alapú szervert használni.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Úgy tűnik, hogy ez a %s példány 32-bites PHP környezetben fut és az open_basedir konfigurálva van a php.ini fájlban. Ez 4 GB-nál nagyobb fájlok esetén problémákat okozhat így erősen ellenjavallt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Kérlek távolítsd el az open_basedir beállítást a php.ini-ből, vagy válts 64bit-es PHP-ra.", + "Set an admin username." : "Állítson be egy rendszergazdai felhasználónevet!", + "Set an admin password." : "Állítson be egy rendszergazdai jelszót!", + "Can't create or write into the data directory %s" : "Nem sikerült létrehozni vagy irni a \"data\" könyvtárba %s", + "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Az %s megosztási alrendszernek támogatnia kell az OCP\\Share_Backend interface-t", + "Sharing backend %s not found" : "A %s megosztási alrendszer nem található", + "Sharing backend for %s not found" : "%s megosztási alrendszere nem található", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s megosztotta veled »%2$s« és hozzá akarja adni:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s megosztotta veled »%2$s« és hozzá akarja adni", + "»%s« added a note to a file shared with you" : "»%s« megjegyést fűzött a veled megosztott fájlhoz", + "Open »%s«" : "»%s« megnyitása", + "%1$s via %2$s" : "%1$s - %2$s", + "You are not allowed to share %s" : "Nincs jogosultságod %s megosztására", + "Can’t increase permissions of %s" : "A(z) %s engedélyei nem kibővíthetők", + "Files can’t be shared with delete permissions" : "A fájlok nem megoszthatók törlési joggal", + "Files can’t be shared with create permissions" : "Fájlok nem oszthatók meg létrehozási joggal", + "Expiration date is in the past" : "A lejárati dátum már elmúlt", + "Can’t set expiration date more than %s days in the future" : "Nem lehet %s napnál későbbi lejáratot megadni", + "%1$s shared »%2$s« with you" : "%1$s megosztotta veled »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s megosztotta veled »%2$s«.", + "Click the button below to open it." : "Kattintson a lenti gombra a megnyitáshoz.", + "The requested share does not exist anymore" : "A kért megosztás már nem létezik", + "Could not find category \"%s\"" : "Ez a kategória nem található: \"%s\"", + "Sunday" : "Vasárnap", + "Monday" : "Hétfő", + "Tuesday" : "Kedd", + "Wednesday" : "Szerda", + "Thursday" : "Csütörtök", + "Friday" : "Péntek", + "Saturday" : "Szombat", + "Sun." : "Vas.", + "Mon." : "Hé.", + "Tue." : "Ke.", + "Wed." : "Sze.", + "Thu." : "Csü.", + "Fri." : "Pén.", + "Sat." : "Szo.", + "Su" : "Va", + "Mo" : "Hé", + "Tu" : "Ke", + "We" : "Sze", + "Th" : "Cs", + "Fr" : "Pé", + "Sa" : "Szo", + "January" : "Január", + "February" : "Február", + "March" : "Március", + "April" : "Április", + "May" : "Május", + "June" : "Június", + "July" : "Július", + "August" : "Augusztus", + "September" : "Szeptember", + "October" : "Október", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Már.", + "Apr." : "Ápr.", + "May." : "Máj.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Aug.", + "Sep." : "Szep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "A felhasználónévben csak a következő karakterek engedélyezettek: \"a-z\", \"A-Z\", \"0-9\", és \"_.@-'\"", + "A valid username must be provided" : "Érvényes felhasználónevet kell megadnia", + "Username contains whitespace at the beginning or at the end" : "A felhasználónév szóközt tartalmaz az elején vagy a végén", + "Username must not consist of dots only" : "A felhasználónév nem állhat csak pontokból", + "A valid password must be provided" : "Érvényes jelszót kell megadnia", + "The username is already being used" : "Ez a bejelentkezési név már foglalt", + "Could not create user" : "Nem sikerült létrehozni a felhasználót", + "User disabled" : "Felhasználó letiltva", + "Login canceled by app" : "Bejelentkezés megszakítva az alkalmazás által", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "\"%1$s\" alkalmazást nem lehet telepíteni, mert a következő függőségek nem teljesülnek: %2$s", + "a safe home for all your data" : "egy biztonságos hely az adataidnak", + "File is currently busy, please try again later" : "A fájl jelenleg elfoglalt, kérjük próbáld újra később!", + "Can't read file" : "Nem olvasható a fájl", + "Application is not enabled" : "Az alkalmazás nincs engedélyezve", + "Authentication error" : "Azonosítási hiba", + "Token expired. Please reload page." : "A token lejárt. Frissítse az oldalt.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nincs telepítve adatbázis-meghajtóprogram (sqlite, mysql vagy postgresql).", + "Cannot write into \"config\" directory" : "Nem írható a \"config\" könyvtár", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ez rendszerint úgy oldható meg, hogy írási jogot adunk a webszervernek a config könyvtárra. Lásd: %s", + "Cannot write into \"apps\" directory" : "Nem írható az \"apps\" könyvtár", + "Cannot create \"data\" directory" : "\"data\" mappa nem hozható létre", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ez legtöbbször megoldható a gyökér mappára a webszervernek adott írási joggal. Lásd: %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Ez legtöbbször megoldható a gyökér mappára a webszervernek adott írási joggal. Lásd: %s.", + "Setting locale to %s failed" : "A lokalizáció %s-re való állítása nem sikerült", + "Please install one of these locales on your system and restart your webserver." : "Kérjük állítsa be a következő lokalizációk valamelyikét a rendszeren és indítsa újra a webszervert!", + "PHP module %s not installed." : "A %s PHP modul nincs telepítve.", + "Please ask your server administrator to install the module." : "Kérje meg a rendszergazdát, hogy telepítse a modult!", + "PHP setting \"%s\" is not set to \"%s\"." : "%s PHP beállítás nincs \"%s\"-re állítva.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "A beállítás változtatása a php.ini fájlban újra futtatja a Nexcloud-ot", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload értéke: \"%s\" az elvárt \"0\" helyett", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "A probléma javításához állítsd a mbstring.func_overload értékét 0-ra a php.ini fájlban.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Legalább libxml2 2.7.0 szükséges. Jelenleg telepített: %s", + "To fix this issue update your libxml2 version and restart your web server." : "A probléma javításához frissítsd a libxml2 verziót és indítsd újra a webszervert.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Úgy tűnik, hogy a PHP úgy van beállítva, hogy eltávolítja programok belsejében elhelyezett szövegblokkokat. Emiatt a rendszer több alapvető fontosságú eleme működésképtelen lesz.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ezt valószínűleg egy gyorsítótár ill. kódgyorsító, mint pl, a Zend, OPcache vagy eAccelererator okozza.", + "PHP modules have been installed, but they are still listed as missing?" : "A PHP modulok telepítve vannak, de a listában mégsincsenek felsorolva?", + "Please ask your server administrator to restart the web server." : "Kérje meg a rendszergazdát, hogy indítsa újra a webszervert!", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 szükséges", + "Please upgrade your database version" : "Kérem frissítse az adatbázis-szoftvert!", + "Your data directory is readable by other users" : "Az adatkönyvtára mások által olvasható", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Kérjük módosítsa a könyvtár elérhetőségi engedélybeállítását 0770-re, hogy a tartalmát más felhasználó ne listázhassa!", + "Your data directory must be an absolute path" : "Az adatkönyvtára abszolút útvonal kell legyen", + "Check the value of \"datadirectory\" in your configuration" : "Ellenőrizd a \"datadirectory\" értékét a konfigurációban", + "Your data directory is invalid" : "Az adatkönyvtárad érvénytelen", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Győződj meg róla, hogy az adatmappa gyökerében legyen egy \".ocdata\" nevű fájl", + "Action \"%s\" not supported or implemented." : "\"%s\" művelet nem támogatott vagy nem ismert.", + "Authentication failed, wrong token or provider ID given" : "Azonosítás sikertelen, hibás token vagy szolgáltató lett megadva", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "A következő paraméterek hiányoznak, hogy végrehajtható legyen a kérés. Paraméterek: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" már hasznalatban van a Felhő szolgáltatónál: \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" ID nem létezik a Felhő szolgáltatónal.", + "Could not obtain lock type %d on \"%s\"." : "Nem sikerült %d típusú zárolást elérni itt: \"%s\".", + "Storage unauthorized. %s" : "A tároló jogosulatlan. %s", + "Storage incomplete configuration. %s" : "A tároló beállítása nem teljes. %s", + "Storage connection error. %s" : "Tároló kapcsolódási hiba. %s", + "Storage is temporarily not available" : "A tároló átmenetileg nem érthető el", + "Storage connection timeout. %s" : "Tároló kapcsolat időtúllépés. %s", + "Following databases are supported: %s" : "A következő adatbázisok támogatottak: %s", + "Following platforms are supported: %s" : "Ezek a platformok támogatottak: %s", + "Overview" : "Áttekintés", + "Basic settings" : "Alapvető beállítások", + "Sharing" : "Megosztás", + "Security" : "Biztonság", + "Groupware" : "Csoportmunka", + "Personal info" : "Személyes információk", + "Mobile & desktop" : "Mobil és deszktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ez legtöbbször megoldható az app mappára a webszervernek adott írási joggal, vagy a config fájlban az alkalmazástár letiltásával. Lásd: %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/hu.json b/docker/overlays/nextcloud/html/lib/l10n/hu.json new file mode 100644 index 0000000..eb79bac --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hu.json @@ -0,0 +1,214 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nem írható a \"config\" könyvtár!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ez rendszerint úgy oldható meg, hogy írási jogot adunk a webszervernek a config könyvtárra.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Avagy, ha jobbnak tűnik tarthatod a config.php fájlt olvashatónak, csak engedélyezd a \"config_is_read_only\" kapcsolót.", + "See %s" : "Lásd %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Avagy, ha jobbnak tűnik tarthatod a config.php fájlt olvashatónak, csak engedélyezd a \"config_is_read_only\" kapcsolót. Továbbiak itt:%s", + "Sample configuration detected" : "A példabeállítások vannak beállítva", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Úgy tűnik a példakonfigurációt próbálja ténylegesen használni. Ez nem támogatott, és működésképtelenné teheti a telepítést. Kérlek olvasd el a dokumentációt és azt követően változtas a config.php-n!", + "%1$s and %2$s" : "%1$s és %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s és %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s és %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s és %5$s", + "Education Edition" : "Oktatási verzió", + "Enterprise bundle" : "Vállalati csomag", + "Groupware bundle" : "Csoportmunka csomag", + "Social sharing bundle" : "Közösségi megosztás csomag", + "PHP %s or higher is required." : "PHP %s vagy ennél újabb szükséges.", + "PHP with a version lower than %s is required." : "Ennél régebbi PHP szükséges: %s.", + "%sbit or higher PHP required." : "%sbites vagy újabb PHP szükséges.", + "The command line tool %s could not be found" : "A parancssori eszköz nem található: %s", + "The library %s is not available." : "A könyvtár %s nem áll rendelkezésre.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s könyvtár %2$s vagy újabb verziója szükséges - elérhető verzió: %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s könyvtár %2$s vagy régebbi verziója szükséges - elérhető verzió: %3$s.", + "Server version %s or higher is required." : "%s vagy újabb szerver verzió szükséges.", + "Server version %s or lower is required." : "%s vagy régebbi szerver verzió szükséges.", + "Logged in user must be an admin" : "A bejelentkezett felhasználónak rendszergazdának kell lennie", + "Authentication" : "Hitelesítés", + "Unknown filetype" : "Ismeretlen fájl típus", + "Invalid image" : "Hibás kép", + "Avatar image is not square" : "Az avatár kép nem négyzetes.", + "today" : "ma", + "tomorrow" : "holnap", + "yesterday" : "tegnap", + "_in %n day_::_in %n days_" : ["%n napon belül","%n napon belül"], + "_%n day ago_::_%n days ago_" : ["%n napja","%n napja"], + "next month" : "következő hónap", + "last month" : "múlt hónapban", + "_in %n month_::_in %n months_" : ["%n hónapon belül","%n hónapon belül"], + "_%n month ago_::_%n months ago_" : ["%n hónapja","%n hónapja"], + "next year" : "következő évben", + "last year" : "tavaly", + "_in %n year_::_in %n years_" : ["%n éven belül","%n éven belül"], + "_%n year ago_::_%n years ago_" : ["%n éve","%n éve"], + "_in %n hour_::_in %n hours_" : ["%n órán belül","%n órán belül"], + "_%n hour ago_::_%n hours ago_" : ["%n órája","%n órája"], + "_in %n minute_::_in %n minutes_" : ["%n percen belül","%n percen belül"], + "_%n minute ago_::_%n minutes ago_" : ["%n perce","%n perce"], + "in a few seconds" : "pár másodpercen belül", + "seconds ago" : "pár másodperce", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "A(z) %s azonosítójú modul nem létezik. Kérlek engedélyezd az app beállításaidban, vagy lépj kapcsolatba a rendszergazdával.", + "File name is a reserved word" : "A fajl neve egy rezervált szó", + "File name contains at least one invalid character" : "A fájlnév legalább egy érvénytelen karaktert tartalmaz!", + "File name is too long" : "A fájlnév túl hosszú!", + "Dot files are not allowed" : "Pontozott fájlok nem engedétlyezettek", + "Empty filename is not allowed" : "Üres fájlnév nem engedétlyezett", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "\"%s\" alkalmazás nem lehet telepíteni, mert az appinfo fájl nem olvasható.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" alkalmazás nem lehet telepíteni, mert nem kompatibilis a szerver jelen verziójával.", + "__language_name__" : "Magyar", + "This is an automatically sent email, please do not reply." : "Ez egy automatikusan küldött levél, kérlek ne válaszolj rá.", + "Help" : "Súgó", + "Apps" : "Alkalmazások", + "Settings" : "Beállítások", + "Log out" : "Kijelentkezés", + "Users" : "Felhasználók", + "Unknown user" : "Ismeretlen felhasználó", + "Additional settings" : "További beállítások", + "%s enter the database username and name." : "%s add meg az adatbázis nevét és felhasználónevét", + "%s enter the database username." : "%s adja meg az adatbázist elérő felhasználó login nevét.", + "%s enter the database name." : "%s adja meg az adatbázis nevét.", + "%s you may not use dots in the database name" : "%s az adatbázis neve nem tartalmazhat pontot", + "You need to enter details of an existing account." : "Egy már létező fiók adatait kell megadnod.", + "Oracle connection could not be established" : "Az Oracle kapcsolat nem hozható létre", + "Oracle username and/or password not valid" : "Az Oracle felhasználói név és/vagy jelszó érvénytelen", + "PostgreSQL username and/or password not valid" : "A PostgreSQL felhasználói név és/vagy jelszó érvénytelen", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "A Mac OS X nem támogatott és %s nem lesz teljesen működőképes. Csak saját felelősségre használja!", + "For the best results, please consider using a GNU/Linux server instead." : "A legjobb eredmény érdekében érdemes GNU/Linux-alapú szervert használni.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Úgy tűnik, hogy ez a %s példány 32-bites PHP környezetben fut és az open_basedir konfigurálva van a php.ini fájlban. Ez 4 GB-nál nagyobb fájlok esetén problémákat okozhat így erősen ellenjavallt.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Kérlek távolítsd el az open_basedir beállítást a php.ini-ből, vagy válts 64bit-es PHP-ra.", + "Set an admin username." : "Állítson be egy rendszergazdai felhasználónevet!", + "Set an admin password." : "Állítson be egy rendszergazdai jelszót!", + "Can't create or write into the data directory %s" : "Nem sikerült létrehozni vagy irni a \"data\" könyvtárba %s", + "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Az %s megosztási alrendszernek támogatnia kell az OCP\\Share_Backend interface-t", + "Sharing backend %s not found" : "A %s megosztási alrendszer nem található", + "Sharing backend for %s not found" : "%s megosztási alrendszere nem található", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s megosztotta veled »%2$s« és hozzá akarja adni:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s megosztotta veled »%2$s« és hozzá akarja adni", + "»%s« added a note to a file shared with you" : "»%s« megjegyést fűzött a veled megosztott fájlhoz", + "Open »%s«" : "»%s« megnyitása", + "%1$s via %2$s" : "%1$s - %2$s", + "You are not allowed to share %s" : "Nincs jogosultságod %s megosztására", + "Can’t increase permissions of %s" : "A(z) %s engedélyei nem kibővíthetők", + "Files can’t be shared with delete permissions" : "A fájlok nem megoszthatók törlési joggal", + "Files can’t be shared with create permissions" : "Fájlok nem oszthatók meg létrehozási joggal", + "Expiration date is in the past" : "A lejárati dátum már elmúlt", + "Can’t set expiration date more than %s days in the future" : "Nem lehet %s napnál későbbi lejáratot megadni", + "%1$s shared »%2$s« with you" : "%1$s megosztotta veled »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s megosztotta veled »%2$s«.", + "Click the button below to open it." : "Kattintson a lenti gombra a megnyitáshoz.", + "The requested share does not exist anymore" : "A kért megosztás már nem létezik", + "Could not find category \"%s\"" : "Ez a kategória nem található: \"%s\"", + "Sunday" : "Vasárnap", + "Monday" : "Hétfő", + "Tuesday" : "Kedd", + "Wednesday" : "Szerda", + "Thursday" : "Csütörtök", + "Friday" : "Péntek", + "Saturday" : "Szombat", + "Sun." : "Vas.", + "Mon." : "Hé.", + "Tue." : "Ke.", + "Wed." : "Sze.", + "Thu." : "Csü.", + "Fri." : "Pén.", + "Sat." : "Szo.", + "Su" : "Va", + "Mo" : "Hé", + "Tu" : "Ke", + "We" : "Sze", + "Th" : "Cs", + "Fr" : "Pé", + "Sa" : "Szo", + "January" : "Január", + "February" : "Február", + "March" : "Március", + "April" : "Április", + "May" : "Május", + "June" : "Június", + "July" : "Július", + "August" : "Augusztus", + "September" : "Szeptember", + "October" : "Október", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Már.", + "Apr." : "Ápr.", + "May." : "Máj.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Aug.", + "Sep." : "Szep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "A felhasználónévben csak a következő karakterek engedélyezettek: \"a-z\", \"A-Z\", \"0-9\", és \"_.@-'\"", + "A valid username must be provided" : "Érvényes felhasználónevet kell megadnia", + "Username contains whitespace at the beginning or at the end" : "A felhasználónév szóközt tartalmaz az elején vagy a végén", + "Username must not consist of dots only" : "A felhasználónév nem állhat csak pontokból", + "A valid password must be provided" : "Érvényes jelszót kell megadnia", + "The username is already being used" : "Ez a bejelentkezési név már foglalt", + "Could not create user" : "Nem sikerült létrehozni a felhasználót", + "User disabled" : "Felhasználó letiltva", + "Login canceled by app" : "Bejelentkezés megszakítva az alkalmazás által", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "\"%1$s\" alkalmazást nem lehet telepíteni, mert a következő függőségek nem teljesülnek: %2$s", + "a safe home for all your data" : "egy biztonságos hely az adataidnak", + "File is currently busy, please try again later" : "A fájl jelenleg elfoglalt, kérjük próbáld újra később!", + "Can't read file" : "Nem olvasható a fájl", + "Application is not enabled" : "Az alkalmazás nincs engedélyezve", + "Authentication error" : "Azonosítási hiba", + "Token expired. Please reload page." : "A token lejárt. Frissítse az oldalt.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nincs telepítve adatbázis-meghajtóprogram (sqlite, mysql vagy postgresql).", + "Cannot write into \"config\" directory" : "Nem írható a \"config\" könyvtár", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ez rendszerint úgy oldható meg, hogy írási jogot adunk a webszervernek a config könyvtárra. Lásd: %s", + "Cannot write into \"apps\" directory" : "Nem írható az \"apps\" könyvtár", + "Cannot create \"data\" directory" : "\"data\" mappa nem hozható létre", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ez legtöbbször megoldható a gyökér mappára a webszervernek adott írási joggal. Lásd: %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Ez legtöbbször megoldható a gyökér mappára a webszervernek adott írási joggal. Lásd: %s.", + "Setting locale to %s failed" : "A lokalizáció %s-re való állítása nem sikerült", + "Please install one of these locales on your system and restart your webserver." : "Kérjük állítsa be a következő lokalizációk valamelyikét a rendszeren és indítsa újra a webszervert!", + "PHP module %s not installed." : "A %s PHP modul nincs telepítve.", + "Please ask your server administrator to install the module." : "Kérje meg a rendszergazdát, hogy telepítse a modult!", + "PHP setting \"%s\" is not set to \"%s\"." : "%s PHP beállítás nincs \"%s\"-re állítva.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "A beállítás változtatása a php.ini fájlban újra futtatja a Nexcloud-ot", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload értéke: \"%s\" az elvárt \"0\" helyett", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "A probléma javításához állítsd a mbstring.func_overload értékét 0-ra a php.ini fájlban.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Legalább libxml2 2.7.0 szükséges. Jelenleg telepített: %s", + "To fix this issue update your libxml2 version and restart your web server." : "A probléma javításához frissítsd a libxml2 verziót és indítsd újra a webszervert.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Úgy tűnik, hogy a PHP úgy van beállítva, hogy eltávolítja programok belsejében elhelyezett szövegblokkokat. Emiatt a rendszer több alapvető fontosságú eleme működésképtelen lesz.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ezt valószínűleg egy gyorsítótár ill. kódgyorsító, mint pl, a Zend, OPcache vagy eAccelererator okozza.", + "PHP modules have been installed, but they are still listed as missing?" : "A PHP modulok telepítve vannak, de a listában mégsincsenek felsorolva?", + "Please ask your server administrator to restart the web server." : "Kérje meg a rendszergazdát, hogy indítsa újra a webszervert!", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 szükséges", + "Please upgrade your database version" : "Kérem frissítse az adatbázis-szoftvert!", + "Your data directory is readable by other users" : "Az adatkönyvtára mások által olvasható", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Kérjük módosítsa a könyvtár elérhetőségi engedélybeállítását 0770-re, hogy a tartalmát más felhasználó ne listázhassa!", + "Your data directory must be an absolute path" : "Az adatkönyvtára abszolút útvonal kell legyen", + "Check the value of \"datadirectory\" in your configuration" : "Ellenőrizd a \"datadirectory\" értékét a konfigurációban", + "Your data directory is invalid" : "Az adatkönyvtárad érvénytelen", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Győződj meg róla, hogy az adatmappa gyökerében legyen egy \".ocdata\" nevű fájl", + "Action \"%s\" not supported or implemented." : "\"%s\" művelet nem támogatott vagy nem ismert.", + "Authentication failed, wrong token or provider ID given" : "Azonosítás sikertelen, hibás token vagy szolgáltató lett megadva", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "A következő paraméterek hiányoznak, hogy végrehajtható legyen a kérés. Paraméterek: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" már hasznalatban van a Felhő szolgáltatónál: \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" ID nem létezik a Felhő szolgáltatónal.", + "Could not obtain lock type %d on \"%s\"." : "Nem sikerült %d típusú zárolást elérni itt: \"%s\".", + "Storage unauthorized. %s" : "A tároló jogosulatlan. %s", + "Storage incomplete configuration. %s" : "A tároló beállítása nem teljes. %s", + "Storage connection error. %s" : "Tároló kapcsolódási hiba. %s", + "Storage is temporarily not available" : "A tároló átmenetileg nem érthető el", + "Storage connection timeout. %s" : "Tároló kapcsolat időtúllépés. %s", + "Following databases are supported: %s" : "A következő adatbázisok támogatottak: %s", + "Following platforms are supported: %s" : "Ezek a platformok támogatottak: %s", + "Overview" : "Áttekintés", + "Basic settings" : "Alapvető beállítások", + "Sharing" : "Megosztás", + "Security" : "Biztonság", + "Groupware" : "Csoportmunka", + "Personal info" : "Személyes információk", + "Mobile & desktop" : "Mobil és deszktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ez legtöbbször megoldható az app mappára a webszervernek adott írási joggal, vagy a config fájlban az alkalmazástár letiltásával. Lásd: %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/hy.js b/docker/overlays/nextcloud/html/lib/l10n/hy.js new file mode 100644 index 0000000..87f9e65 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hy.js @@ -0,0 +1,57 @@ +OC.L10N.register( + "lib", + { + "today" : "այսօր", + "seconds ago" : "վրկ. առաջ", + "File name contains at least one invalid character" : "Ֆայլի անունը առնվազն մի անվավեր նիշ է պարունակում", + "__language_name__" : "Հայերեն", + "Help" : "Օգնություն", + "Settings" : "կարգավորումներ", + "Log out" : "Դուրս գալ", + "Sunday" : "Կիրակի", + "Monday" : "Երկուշաբթի", + "Tuesday" : "Երեքշաբթի", + "Wednesday" : "Չորեքշաբթի", + "Thursday" : "Հինգշաբթի", + "Friday" : "Ուրբաթ", + "Saturday" : "Շաբաթ", + "Sun." : "Կիր.", + "Mon." : "Երկ.", + "Tue." : "Երք.", + "Wed." : "Չոր.", + "Thu." : "Հնգ.", + "Fri." : "Ուրբ.", + "Sat." : "Շաբ.", + "Su" : "Կիր", + "Mo" : "Երկ", + "Tu" : "Երք", + "We" : "Չոր", + "Th" : "Հնգ", + "Fr" : "Ուրբ", + "Sa" : "Շաբ", + "January" : "Հունվար", + "February" : "Փետրվար", + "March" : "Մարտ", + "April" : "Ապրիլ", + "May" : "Մայիս", + "June" : "Հունիս", + "July" : "Հուլիս", + "August" : "Օգոստոս", + "September" : "Սեպտեմբեր", + "October" : "Հոկտեմբեր", + "November" : "Նոյեմբեր", + "December" : "Դեկտեմբեր", + "Jan." : "Հնվ.", + "Feb." : "Փտվ.", + "Mar." : "Մրտ.", + "Apr." : "Ապր.", + "May." : "Մյս.", + "Jun." : "Հնս.", + "Jul." : "Հլս.", + "Aug." : "Օգս.", + "Sep." : "Սեպ.", + "Oct." : "Հոկ.", + "Nov." : "Նոյ.", + "Dec." : "Դեկ." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/hy.json b/docker/overlays/nextcloud/html/lib/l10n/hy.json new file mode 100644 index 0000000..592eb6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/hy.json @@ -0,0 +1,55 @@ +{ "translations": { + "today" : "այսօր", + "seconds ago" : "վրկ. առաջ", + "File name contains at least one invalid character" : "Ֆայլի անունը առնվազն մի անվավեր նիշ է պարունակում", + "__language_name__" : "Հայերեն", + "Help" : "Օգնություն", + "Settings" : "կարգավորումներ", + "Log out" : "Դուրս գալ", + "Sunday" : "Կիրակի", + "Monday" : "Երկուշաբթի", + "Tuesday" : "Երեքշաբթի", + "Wednesday" : "Չորեքշաբթի", + "Thursday" : "Հինգշաբթի", + "Friday" : "Ուրբաթ", + "Saturday" : "Շաբաթ", + "Sun." : "Կիր.", + "Mon." : "Երկ.", + "Tue." : "Երք.", + "Wed." : "Չոր.", + "Thu." : "Հնգ.", + "Fri." : "Ուրբ.", + "Sat." : "Շաբ.", + "Su" : "Կիր", + "Mo" : "Երկ", + "Tu" : "Երք", + "We" : "Չոր", + "Th" : "Հնգ", + "Fr" : "Ուրբ", + "Sa" : "Շաբ", + "January" : "Հունվար", + "February" : "Փետրվար", + "March" : "Մարտ", + "April" : "Ապրիլ", + "May" : "Մայիս", + "June" : "Հունիս", + "July" : "Հուլիս", + "August" : "Օգոստոս", + "September" : "Սեպտեմբեր", + "October" : "Հոկտեմբեր", + "November" : "Նոյեմբեր", + "December" : "Դեկտեմբեր", + "Jan." : "Հնվ.", + "Feb." : "Փտվ.", + "Mar." : "Մրտ.", + "Apr." : "Ապր.", + "May." : "Մյս.", + "Jun." : "Հնս.", + "Jul." : "Հլս.", + "Aug." : "Օգս.", + "Sep." : "Սեպ.", + "Oct." : "Հոկ.", + "Nov." : "Նոյ.", + "Dec." : "Դեկ." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ia.js b/docker/overlays/nextcloud/html/lib/l10n/ia.js new file mode 100644 index 0000000..eccb308 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ia.js @@ -0,0 +1,67 @@ +OC.L10N.register( + "lib", + { + "Authentication" : "Authentication", + "Unknown filetype" : "Typo de file incognite", + "Invalid image" : "Imagine invalide", + "seconds ago" : "secundas passate", + "__language_name__" : "Interlingua de IALA", + "Help" : "Adjuta", + "Settings" : "Configurationes", + "Log out" : "Clauder session", + "Users" : "Usatores", + "Unknown user" : "Usator incognite", + "Invalid Federated Cloud ID" : "ID del Nube Federate", + "Sunday" : "Dominica", + "Monday" : "Lunedi", + "Tuesday" : "Martedi", + "Wednesday" : "Mercuridi", + "Thursday" : "Jovedi", + "Friday" : "Venerdi", + "Saturday" : "Sabbato", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Jov.", + "Fri." : "Ven.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Jo", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Januario", + "February" : "Februario", + "March" : "Martio", + "April" : "April", + "May" : "Maio", + "June" : "Junio", + "July" : "Julio", + "August" : "Augusto", + "September" : "Septembre", + "October" : "Octobre", + "November" : "Novembre", + "December" : "Decembre", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Un nomine de usator valide debe esser providite", + "A valid password must be provided" : "Un contrasigno valide debe esser providite", + "Authentication error" : "Error in authentication", + "Storage is temporarily not available" : "Immagazinage es provisorimente non disponibile", + "Sharing" : "Compartente", + "Personal info" : "Information personal" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ia.json b/docker/overlays/nextcloud/html/lib/l10n/ia.json new file mode 100644 index 0000000..7774ea1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ia.json @@ -0,0 +1,65 @@ +{ "translations": { + "Authentication" : "Authentication", + "Unknown filetype" : "Typo de file incognite", + "Invalid image" : "Imagine invalide", + "seconds ago" : "secundas passate", + "__language_name__" : "Interlingua de IALA", + "Help" : "Adjuta", + "Settings" : "Configurationes", + "Log out" : "Clauder session", + "Users" : "Usatores", + "Unknown user" : "Usator incognite", + "Invalid Federated Cloud ID" : "ID del Nube Federate", + "Sunday" : "Dominica", + "Monday" : "Lunedi", + "Tuesday" : "Martedi", + "Wednesday" : "Mercuridi", + "Thursday" : "Jovedi", + "Friday" : "Venerdi", + "Saturday" : "Sabbato", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Jov.", + "Fri." : "Ven.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Jo", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Januario", + "February" : "Februario", + "March" : "Martio", + "April" : "April", + "May" : "Maio", + "June" : "Junio", + "July" : "Julio", + "August" : "Augusto", + "September" : "Septembre", + "October" : "Octobre", + "November" : "Novembre", + "December" : "Decembre", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Un nomine de usator valide debe esser providite", + "A valid password must be provided" : "Un contrasigno valide debe esser providite", + "Authentication error" : "Error in authentication", + "Storage is temporarily not available" : "Immagazinage es provisorimente non disponibile", + "Sharing" : "Compartente", + "Personal info" : "Information personal" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/id.js b/docker/overlays/nextcloud/html/lib/l10n/id.js new file mode 100644 index 0000000..dfdcada --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/id.js @@ -0,0 +1,159 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Tidak dapat menulis kedalam direktori \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Hal ini biasanya dapat diperbaiki dengan memberikan akses tulis bagi situs web ke direktori config", + "See %s" : "Lihat %s", + "Sample configuration detected" : "Konfigurasi sampel ditemukan", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Ditemukan bahwa konfigurasi sampel telah disalin. Hal ini dapat merusak instalasi Anda dan tidak didukung. Silahkan baca dokumentasi sebelum melakukan perubahan pada config.php", + "PHP %s or higher is required." : "Diperlukan PHP %s atau yang lebih tinggi.", + "PHP with a version lower than %s is required." : "Diperlukan PHP dengan versi yang lebh rendah dari %s.", + "%sbit or higher PHP required." : "PHP %sbit atau yang lebih tinggi diperlukan.", + "The command line tool %s could not be found" : "Alat baris perintah %s tidak ditemukan", + "The library %s is not available." : "Pustaka %s tidak tersedia.", + "Server version %s or higher is required." : "Server versi %s atau yang lebih tinggi diperlukan.", + "Server version %s or lower is required." : "Server versi %s atau yang lebih rendah diperlukan.", + "Authentication" : "Otentikasi", + "Unknown filetype" : "Tipe berkas tak dikenal", + "Invalid image" : "Gambar tidak sah", + "today" : "hari ini", + "yesterday" : "kemarin", + "_%n day ago_::_%n days ago_" : ["%n hari yang lalu"], + "last month" : "bulan kemarin", + "_%n month ago_::_%n months ago_" : ["%n bulan yang lalu"], + "last year" : "tahun kemarin", + "_%n year ago_::_%n years ago_" : ["%n tahun yang lalu"], + "_%n hour ago_::_%n hours ago_" : ["%n jam yang lalu"], + "_%n minute ago_::_%n minutes ago_" : ["%n menit yang lalu"], + "seconds ago" : "beberapa detik yang lalu", + "File name is a reserved word" : "Nama berkas merupakan kata yang disediakan", + "File name contains at least one invalid character" : "Nama berkas berisi setidaknya satu karakter yang tidak sah.", + "File name is too long" : "Nama berkas terlalu panjang", + "Dot files are not allowed" : "Berkas titik tidak diperbolehkan", + "Empty filename is not allowed" : "Nama berkas kosong tidak diperbolehkan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikasi \"%s\" tidak dapat dipasang karena berkas appinfo tidak dapat dibaca.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikasi \"%s\" tidak dapat dipasang karena tidak kompatibel dengan versi server ini", + "__language_name__" : "Bahasa Indonesia", + "Help" : "Bantuan", + "Apps" : "aplikasi", + "Settings" : "Setelan", + "Log out" : "Keluar", + "Users" : "Pengguna", + "Unknown user" : "Pengguna tidak dikenal", + "Additional settings" : "Setelan tambahan", + "%s enter the database username and name." : "%s masukkan nama pengguna database dan nama database.", + "%s enter the database username." : "%s masukkan nama pengguna basis data.", + "%s enter the database name." : "%s masukkan nama basis data.", + "%s you may not use dots in the database name" : "%s anda tidak boleh menggunakan karakter titik pada nama basis data", + "Oracle connection could not be established" : "Koneksi Oracle tidak dapat dibuat", + "Oracle username and/or password not valid" : "Nama pengguna dan/atau kata sandi Oracle tidak sah", + "PostgreSQL username and/or password not valid" : "Nama pengguna dan/atau kata sandi PostgreSQL tidak valid", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X tidak didukung dan %s tidak akan bekerja dengan baik pada platform ini. Gunakan dengan resiko Anda sendiri!", + "For the best results, please consider using a GNU/Linux server instead." : "Untuk hasil terbaik, pertimbangkan untuk menggunakan server GNU/Linux sebagai gantinya. ", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Kelihatannya instansi %s ini berjalan di lingkungan PHP 32-bit dan open_basedir telah dikonfigurasi di php.ini. Hal ini akan menyebabkan masalah dengan berkas lebih dari 4 GB dan sangat tidak disarankan.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Mohon hapus pengaturan open_basedir didalam php.ini atau beralih ke PHP 64-bit.", + "Set an admin username." : "Tetapkan nama pengguna admin.", + "Set an admin password." : "Tetapkan kata sandi admin.", + "Can't create or write into the data directory %s" : "Tidak dapat membuat atau menulis kedalam direktori data %s", + "Invalid Federated Cloud ID" : "Federated Cloud ID tidak sah", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend berbagi %s harus mengimplementasi antarmuka OCP\\Share_Backend", + "Sharing backend %s not found" : "Backend berbagi %s tidak ditemukan", + "Sharing backend for %s not found" : "Backend berbagi untuk %s tidak ditemukan", + "Open »%s«" : "Buka »%s«", + "You are not allowed to share %s" : "Anda tidak diizinkan untuk membagikan %s", + "Expiration date is in the past" : "Tanggal kedaluwarsa sudah lewat", + "Could not find category \"%s\"" : "Tidak menemukan kategori \"%s\"", + "Sunday" : "Minggu", + "Monday" : "Senin", + "Tuesday" : "Selasa", + "Wednesday" : "Rabu", + "Thursday" : "Kamis", + "Friday" : "Jumat", + "Saturday" : "Sabtu", + "Sun." : "Min.", + "Mon." : "Sen.", + "Tue." : "Sel.", + "Wed." : "Rab.", + "Thu." : "Kam.", + "Fri." : "Jum.", + "Sat." : "Sab.", + "Su" : "Min", + "Mo" : "Sen", + "Tu" : "Sel", + "We" : "Rab", + "Th" : "Kam", + "Fr" : "Jum", + "Sa" : "Sab", + "January" : "Januari", + "February" : "Februari", + "March" : "Maret", + "April" : "April", + "May" : "Mei", + "June" : "Juni", + "July" : "Juli", + "August" : "Agustus", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Agu.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Hanya karakter ini yang diizinkan dalam nama pengguna: \"a-z\", \"A-Z\", \"0-9\", dan \"_.@-'\"", + "A valid username must be provided" : "Tuliskan nama pengguna yang valid", + "Username contains whitespace at the beginning or at the end" : "Nama pengguna mengandung spasi di depan atau di belakang.", + "A valid password must be provided" : "Tuliskan kata sandi yang valid", + "The username is already being used" : "Nama pengguna ini telah digunakan", + "User disabled" : "Pengguna dinonaktifkan", + "Login canceled by app" : "Log masuk dibatalkan oleh aplikasi", + "a safe home for all your data" : "rumah yang aman untuk semua datamu", + "File is currently busy, please try again later" : "Berkas sedang sibuk, mohon coba lagi nanti", + "Can't read file" : "Tidak dapat membaca berkas", + "Application is not enabled" : "aplikasi tidak diaktifkan", + "Authentication error" : "Galat saat otentikasi", + "Token expired. Please reload page." : "Token sudah kedaluwarsa. Silakan muat ulang halaman.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Tidak ada driver (sqlite, mysql, or postgresql) yang terinstal.", + "Cannot write into \"config\" directory" : "Tidak dapat menulis kedalam direktori \"config\"", + "Cannot write into \"apps\" directory" : "Tidak dapat menulis kedalam direktori \"apps\"", + "Setting locale to %s failed" : "Pengaturan lokal ke %s gagal", + "Please install one of these locales on your system and restart your webserver." : "Mohon instal paling tidak satu lokal pada sistem Anda dan jalankan ulang server web.", + "PHP module %s not installed." : "Module PHP %s tidak terinstal.", + "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal module.", + "PHP setting \"%s\" is not set to \"%s\"." : "Pengaturan PHP \"%s\" tidak diatur ke \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Menyesuaikan pengaturan ini di php.ini akan membuat Nextcloud berjalan kembali", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload diatur menjadi \"%s\" bukan nilai yang diharapkan \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Untuk memperbaiki masalah ini, atur mbstring.func_overload menjadi 0 pada berkas php.ini Anda", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Setidaknya libxml2 2.7.0 dibutuhkan. Saat ini %s dipasang.", + "To fix this issue update your libxml2 version and restart your web server." : "Untuk mengatasi masalah ini, perbarui versi libxml2 Anda dan mulai-ulang server web Anda.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Tampaknya PHP diatur untuk memotong inline doc blocks. Hal ini akan menyebabkan beberapa aplikasi inti menjadi tidak dapat diakses.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Hal ini kemungkinan disebabkan oleh cache/akselerator seperti Zend OPcache atau eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Modul PHP telah terinstal, tetapi mereka terlihat tidak ada?", + "Please ask your server administrator to restart the web server." : "Mohon minta administrator Anda untuk menjalankan ulang server web.", + "PostgreSQL >= 9 required" : "Diperlukan PostgreSQL >= 9", + "Please upgrade your database version" : "Mohon perbarui versi basis data Anda", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Mohon ubah perizinan menjadi 0770 sehingga direktori tersebut tidak dapat dilihat oleh pengguna lain.", + "Check the value of \"datadirectory\" in your configuration" : "Periksa nilai \"datadirectory\" di konfigurasi Anda", + "Could not obtain lock type %d on \"%s\"." : "Tidak bisa memperoleh jenis kunci %d pada \"%s\".", + "Storage unauthorized. %s" : "Penyimpanan tidak terotorisasi. %s", + "Storage incomplete configuration. %s" : "Konfigurasi penyimpanan tidak terselesaikan. %s", + "Storage connection error. %s" : "Koneksi penyimpanan bermasalah. %s", + "Storage is temporarily not available" : "Penyimpanan sementara tidak tersedia", + "Storage connection timeout. %s" : "Koneksi penyimpanan waktu-habis. %s", + "Following databases are supported: %s" : "Berikut adalah basis data yang didukung: %s", + "Following platforms are supported: %s" : "Berikut adalah platform yang didukung: %s", + "Overview" : "Ringkasan", + "Basic settings" : "Setelan dasar", + "Sharing" : "Berbagi", + "Security" : "Keamanan", + "Personal info" : "Info pribadi" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/id.json b/docker/overlays/nextcloud/html/lib/l10n/id.json new file mode 100644 index 0000000..4abf993 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/id.json @@ -0,0 +1,157 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Tidak dapat menulis kedalam direktori \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Hal ini biasanya dapat diperbaiki dengan memberikan akses tulis bagi situs web ke direktori config", + "See %s" : "Lihat %s", + "Sample configuration detected" : "Konfigurasi sampel ditemukan", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Ditemukan bahwa konfigurasi sampel telah disalin. Hal ini dapat merusak instalasi Anda dan tidak didukung. Silahkan baca dokumentasi sebelum melakukan perubahan pada config.php", + "PHP %s or higher is required." : "Diperlukan PHP %s atau yang lebih tinggi.", + "PHP with a version lower than %s is required." : "Diperlukan PHP dengan versi yang lebh rendah dari %s.", + "%sbit or higher PHP required." : "PHP %sbit atau yang lebih tinggi diperlukan.", + "The command line tool %s could not be found" : "Alat baris perintah %s tidak ditemukan", + "The library %s is not available." : "Pustaka %s tidak tersedia.", + "Server version %s or higher is required." : "Server versi %s atau yang lebih tinggi diperlukan.", + "Server version %s or lower is required." : "Server versi %s atau yang lebih rendah diperlukan.", + "Authentication" : "Otentikasi", + "Unknown filetype" : "Tipe berkas tak dikenal", + "Invalid image" : "Gambar tidak sah", + "today" : "hari ini", + "yesterday" : "kemarin", + "_%n day ago_::_%n days ago_" : ["%n hari yang lalu"], + "last month" : "bulan kemarin", + "_%n month ago_::_%n months ago_" : ["%n bulan yang lalu"], + "last year" : "tahun kemarin", + "_%n year ago_::_%n years ago_" : ["%n tahun yang lalu"], + "_%n hour ago_::_%n hours ago_" : ["%n jam yang lalu"], + "_%n minute ago_::_%n minutes ago_" : ["%n menit yang lalu"], + "seconds ago" : "beberapa detik yang lalu", + "File name is a reserved word" : "Nama berkas merupakan kata yang disediakan", + "File name contains at least one invalid character" : "Nama berkas berisi setidaknya satu karakter yang tidak sah.", + "File name is too long" : "Nama berkas terlalu panjang", + "Dot files are not allowed" : "Berkas titik tidak diperbolehkan", + "Empty filename is not allowed" : "Nama berkas kosong tidak diperbolehkan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikasi \"%s\" tidak dapat dipasang karena berkas appinfo tidak dapat dibaca.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikasi \"%s\" tidak dapat dipasang karena tidak kompatibel dengan versi server ini", + "__language_name__" : "Bahasa Indonesia", + "Help" : "Bantuan", + "Apps" : "aplikasi", + "Settings" : "Setelan", + "Log out" : "Keluar", + "Users" : "Pengguna", + "Unknown user" : "Pengguna tidak dikenal", + "Additional settings" : "Setelan tambahan", + "%s enter the database username and name." : "%s masukkan nama pengguna database dan nama database.", + "%s enter the database username." : "%s masukkan nama pengguna basis data.", + "%s enter the database name." : "%s masukkan nama basis data.", + "%s you may not use dots in the database name" : "%s anda tidak boleh menggunakan karakter titik pada nama basis data", + "Oracle connection could not be established" : "Koneksi Oracle tidak dapat dibuat", + "Oracle username and/or password not valid" : "Nama pengguna dan/atau kata sandi Oracle tidak sah", + "PostgreSQL username and/or password not valid" : "Nama pengguna dan/atau kata sandi PostgreSQL tidak valid", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X tidak didukung dan %s tidak akan bekerja dengan baik pada platform ini. Gunakan dengan resiko Anda sendiri!", + "For the best results, please consider using a GNU/Linux server instead." : "Untuk hasil terbaik, pertimbangkan untuk menggunakan server GNU/Linux sebagai gantinya. ", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Kelihatannya instansi %s ini berjalan di lingkungan PHP 32-bit dan open_basedir telah dikonfigurasi di php.ini. Hal ini akan menyebabkan masalah dengan berkas lebih dari 4 GB dan sangat tidak disarankan.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Mohon hapus pengaturan open_basedir didalam php.ini atau beralih ke PHP 64-bit.", + "Set an admin username." : "Tetapkan nama pengguna admin.", + "Set an admin password." : "Tetapkan kata sandi admin.", + "Can't create or write into the data directory %s" : "Tidak dapat membuat atau menulis kedalam direktori data %s", + "Invalid Federated Cloud ID" : "Federated Cloud ID tidak sah", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend berbagi %s harus mengimplementasi antarmuka OCP\\Share_Backend", + "Sharing backend %s not found" : "Backend berbagi %s tidak ditemukan", + "Sharing backend for %s not found" : "Backend berbagi untuk %s tidak ditemukan", + "Open »%s«" : "Buka »%s«", + "You are not allowed to share %s" : "Anda tidak diizinkan untuk membagikan %s", + "Expiration date is in the past" : "Tanggal kedaluwarsa sudah lewat", + "Could not find category \"%s\"" : "Tidak menemukan kategori \"%s\"", + "Sunday" : "Minggu", + "Monday" : "Senin", + "Tuesday" : "Selasa", + "Wednesday" : "Rabu", + "Thursday" : "Kamis", + "Friday" : "Jumat", + "Saturday" : "Sabtu", + "Sun." : "Min.", + "Mon." : "Sen.", + "Tue." : "Sel.", + "Wed." : "Rab.", + "Thu." : "Kam.", + "Fri." : "Jum.", + "Sat." : "Sab.", + "Su" : "Min", + "Mo" : "Sen", + "Tu" : "Sel", + "We" : "Rab", + "Th" : "Kam", + "Fr" : "Jum", + "Sa" : "Sab", + "January" : "Januari", + "February" : "Februari", + "March" : "Maret", + "April" : "April", + "May" : "Mei", + "June" : "Juni", + "July" : "Juli", + "August" : "Agustus", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Agu.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Hanya karakter ini yang diizinkan dalam nama pengguna: \"a-z\", \"A-Z\", \"0-9\", dan \"_.@-'\"", + "A valid username must be provided" : "Tuliskan nama pengguna yang valid", + "Username contains whitespace at the beginning or at the end" : "Nama pengguna mengandung spasi di depan atau di belakang.", + "A valid password must be provided" : "Tuliskan kata sandi yang valid", + "The username is already being used" : "Nama pengguna ini telah digunakan", + "User disabled" : "Pengguna dinonaktifkan", + "Login canceled by app" : "Log masuk dibatalkan oleh aplikasi", + "a safe home for all your data" : "rumah yang aman untuk semua datamu", + "File is currently busy, please try again later" : "Berkas sedang sibuk, mohon coba lagi nanti", + "Can't read file" : "Tidak dapat membaca berkas", + "Application is not enabled" : "aplikasi tidak diaktifkan", + "Authentication error" : "Galat saat otentikasi", + "Token expired. Please reload page." : "Token sudah kedaluwarsa. Silakan muat ulang halaman.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Tidak ada driver (sqlite, mysql, or postgresql) yang terinstal.", + "Cannot write into \"config\" directory" : "Tidak dapat menulis kedalam direktori \"config\"", + "Cannot write into \"apps\" directory" : "Tidak dapat menulis kedalam direktori \"apps\"", + "Setting locale to %s failed" : "Pengaturan lokal ke %s gagal", + "Please install one of these locales on your system and restart your webserver." : "Mohon instal paling tidak satu lokal pada sistem Anda dan jalankan ulang server web.", + "PHP module %s not installed." : "Module PHP %s tidak terinstal.", + "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal module.", + "PHP setting \"%s\" is not set to \"%s\"." : "Pengaturan PHP \"%s\" tidak diatur ke \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Menyesuaikan pengaturan ini di php.ini akan membuat Nextcloud berjalan kembali", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload diatur menjadi \"%s\" bukan nilai yang diharapkan \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Untuk memperbaiki masalah ini, atur mbstring.func_overload menjadi 0 pada berkas php.ini Anda", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Setidaknya libxml2 2.7.0 dibutuhkan. Saat ini %s dipasang.", + "To fix this issue update your libxml2 version and restart your web server." : "Untuk mengatasi masalah ini, perbarui versi libxml2 Anda dan mulai-ulang server web Anda.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Tampaknya PHP diatur untuk memotong inline doc blocks. Hal ini akan menyebabkan beberapa aplikasi inti menjadi tidak dapat diakses.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Hal ini kemungkinan disebabkan oleh cache/akselerator seperti Zend OPcache atau eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Modul PHP telah terinstal, tetapi mereka terlihat tidak ada?", + "Please ask your server administrator to restart the web server." : "Mohon minta administrator Anda untuk menjalankan ulang server web.", + "PostgreSQL >= 9 required" : "Diperlukan PostgreSQL >= 9", + "Please upgrade your database version" : "Mohon perbarui versi basis data Anda", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Mohon ubah perizinan menjadi 0770 sehingga direktori tersebut tidak dapat dilihat oleh pengguna lain.", + "Check the value of \"datadirectory\" in your configuration" : "Periksa nilai \"datadirectory\" di konfigurasi Anda", + "Could not obtain lock type %d on \"%s\"." : "Tidak bisa memperoleh jenis kunci %d pada \"%s\".", + "Storage unauthorized. %s" : "Penyimpanan tidak terotorisasi. %s", + "Storage incomplete configuration. %s" : "Konfigurasi penyimpanan tidak terselesaikan. %s", + "Storage connection error. %s" : "Koneksi penyimpanan bermasalah. %s", + "Storage is temporarily not available" : "Penyimpanan sementara tidak tersedia", + "Storage connection timeout. %s" : "Koneksi penyimpanan waktu-habis. %s", + "Following databases are supported: %s" : "Berikut adalah basis data yang didukung: %s", + "Following platforms are supported: %s" : "Berikut adalah platform yang didukung: %s", + "Overview" : "Ringkasan", + "Basic settings" : "Setelan dasar", + "Sharing" : "Berbagi", + "Security" : "Keamanan", + "Personal info" : "Info pribadi" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/is.js b/docker/overlays/nextcloud/html/lib/l10n/is.js new file mode 100644 index 0000000..cdf836b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/is.js @@ -0,0 +1,231 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Get ekki skrifað í \"config\" möppuna!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni.", + "See %s" : "Skoðaðu %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni. Skoðaðu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Skrám forritsins %$1s var ekki rétt skipt út. Gakktu úr skugga um að þetta sé útgáfa sem sé samhæfð útgáfu vefþjónsins.", + "Sample configuration detected" : "Fann sýnisuppsetningu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Komið hefur í ljós að sýniuppsetningin var afrituð. Þetta getur skemmt uppsetninguna og er ekki stutt. Endilega lestu hjálparskjölin áður en þú gerir breytingar á config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "Education Edition" : "Kennsluútgáfa", + "Enterprise bundle" : "Fyrirtækjavöndull", + "Groupware bundle" : "Hópvinnsluvöndull", + "Social sharing bundle" : "Deilivöndull fyrir samfélagsmiðla", + "PHP %s or higher is required." : "Krafist er PHP %s eða hærra.", + "PHP with a version lower than %s is required." : "Krafist er PHP útgáfu %s eða lægri.", + "%sbit or higher PHP required." : "Krafist er PHP %sbita eða hærra.", + "The command line tool %s could not be found" : "Skipanalínutólið \"%s\" fannst ekki", + "The library %s is not available." : "Aðgerðasafnið %s er ekki tiltækt.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu hærri en %2$s - tiltæk útgáfa er %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu lægri en %2$s - tiltæk útgáfa er %3$s.", + "Server version %s or higher is required." : "Krafist er þjóns af útgáfu %s eða hærra.", + "Server version %s or lower is required." : "Krafist er þjóns af útgáfu %s eða lægri.", + "Logged in user must be an admin or sub admin" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi", + "Logged in user must be an admin" : "Innskráður notandi verður að vera stjórnandi", + "Wiping of device %s has started" : "Útþurrkun af tækinu %s er byrjuð", + "Wiping of device »%s« has started" : "Útþurrkun af tækinu »%s« er byrjuð", + "»%s« started remote wipe" : "»%s« byrjaði fjartengda útþurrkun", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Tækið eða forritið »%s« hefur hafið ferli til að þurrka út fjartengt. Þú munt fá annan tölvupóst þegar ferlinu er lokið", + "Wiping of device %s has finished" : "Útþurrkun af tækinu %s er lokið", + "Wiping of device »%s« has finished" : "Útþurrkun af tækinu »%s« er lokið", + "»%s« finished remote wipe" : "»%s« lauk fjartengdri útþurrkun", + "Device or application »%s« has finished the remote wipe process." : "Tækið eða forritið »%s« hefur lokið fjartengda útþurrkunarferlinu.", + "Remote wipe started" : "Fjartengd útþurrkun er byrjuð", + "A remote wipe was started on device %s" : "Fjartengd útþurrkun var hafin á tækinu %s", + "Remote wipe finished" : "Fjartengdri útþurrkun er lokið", + "The remote wipe on %s has finished" : "Fjartengd útþurrkun á %s er lokið", + "Authentication" : "Auðkenning", + "Unknown filetype" : "Óþekkt skráategund", + "Invalid image" : "Ógild mynd", + "Avatar image is not square" : "Auðkennismynd er ekki ferningslaga", + "today" : "í dag", + "tomorrow" : "á morgun", + "yesterday" : "í gær", + "_in %n day_::_in %n days_" : ["eftir %n dag","eftir %n daga"], + "_%n day ago_::_%n days ago_" : ["fyrir %n degi síðan","fyrir %n dögum síðan"], + "next month" : "í næsta mánuði", + "last month" : "í síðasta mánuði", + "_in %n month_::_in %n months_" : ["eftir %n mánuð","eftir %n mánuði"], + "_%n month ago_::_%n months ago_" : ["fyrir %n mánuði","fyrir %n mánuðum"], + "next year" : "á næsta ári", + "last year" : "síðasta ári", + "_in %n year_::_in %n years_" : ["eftir %n ár","eftir %n ár"], + "_%n year ago_::_%n years ago_" : ["fyrir %n degi síðan","fyrir %n árum síðan"], + "_in %n hour_::_in %n hours_" : ["eftir %n klukkustund","eftir %n klukkustundir"], + "_%n hour ago_::_%n hours ago_" : ["fyrir %n klukkustund síðan","fyrir %n klukkustundum síðan"], + "_in %n minute_::_in %n minutes_" : ["eftir %n mínútu","eftir %n mínútur"], + "_%n minute ago_::_%n minutes ago_" : ["fyrir %n mínútu síðan","fyrir %n mínútum síðan"], + "in a few seconds" : "eftir örfáar sekúndur", + "seconds ago" : "sekúndum síðan", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Eining með auðkenni: %s er ekki til. Virkjaðu hana í forritastillingum eða hafðu samband við kerfisstjóra.", + "File name is a reserved word" : "Skráarheiti er þegar frátekið orð", + "File name contains at least one invalid character" : "Skráarheitið inniheldur að minnsta kosti einn ógildan staf", + "File name is too long" : "Skráarheiti er of langt", + "Dot files are not allowed" : "Skrár með punkti eru ekki leyfðar", + "Empty filename is not allowed" : "Autt skráarheiti er ekki leyft.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Ekki er hægt að setja upp \"%s\" forritið vegna þess að ekki var hægt að lesa appinfo-skrána.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Ekki var hægt að setja upp forritið \"%s\" vegna þess að það er ekki samhæft þessari útgáfu vefþjónsins.", + "__language_name__" : "Íslenska", + "This is an automatically sent email, please do not reply." : "Þetta er sjálfvirk tölvupóstsending, ekki svara þessu.", + "Help" : "Hjálp", + "Apps" : "Forrit", + "Settings" : "Stillingar", + "Log out" : "Skrá út", + "Users" : "Notendur", + "Unknown user" : "Óþekktur notandi", + "Additional settings" : "Valfrjálsar stillingar", + "%s enter the database username and name." : "%s settu inn notandanafn og nafn á gagnagrunni.", + "%s enter the database username." : "%s settu inn notandanafn í gagnagrunni.", + "%s enter the database name." : "%s settu inn nafn á gagnagrunni.", + "%s you may not use dots in the database name" : "%s þú mátt ekki nota punkta í nafni á gagnagrunni", + "You need to enter details of an existing account." : "Þú verður að setja inn auðkenni fyrirliggjandi notandaaðgangs.", + "Oracle connection could not be established" : "Ekki tókst að koma tengingu á við Oracle", + "Oracle username and/or password not valid" : "Notandanafn eða lykilorð Oracle er ekki gilt", + "PostgreSQL username and/or password not valid" : "Notandanafn eða lykilorð PostgreSQL er ekki gilt", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X er ekki stutt og %s mun ekki vinna eðlilega á þessu stýrikerfi. Notaðu þetta því á þína eigin ábyrgð! ", + "For the best results, please consider using a GNU/Linux server instead." : "Fyrir bestu útkomu ættirðu að íhuga að nota GNU/Linux þjón í staðinn.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Það lítur út eins og þessi %s uppsetning sé að keyra á 32-bita PHP umhverfi og að open_basedir hafi verið stillt í php.ini. Þetta mun valda vandamálum með skrár stærri en 4 GB og er stranglega mælt gegn því að þetta sé gert.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjarlægðu stillinguna open_basedir úr php.ini eða skiptu yfir í 64-bita PHP.", + "Set an admin username." : "Stilltu notandanafn kerfisstjóra.", + "Set an admin password." : "Stilltu lykilorð kerfisstjóra.", + "Can't create or write into the data directory %s" : "Gat ekki búið til eða skrifað í gagnamöppuna %s", + "Invalid Federated Cloud ID" : "Ógilt skýjasambandsauðkenni (Federated Cloud ID)", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Deilingarbakendinn %s verður að vera settur upp fyrir viðmótið OCP\\Share_Backend", + "Sharing backend %s not found" : "Deilingarbakendinn %s fannst ekki", + "Sharing backend for %s not found" : "Deilingarbakendi fyrir %s fannst ekki", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s deildi »%2$s« með þér og vill bæta við:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s deildi »%2$s« með þér og vill bæta við", + "»%s« added a note to a file shared with you" : "»%s« bætti minnispunkti við skrá sem deilt er með þér", + "Open »%s«" : "Opna »%s«", + "%1$s via %2$s" : "%1$s með %2$s", + "You are not allowed to share %s" : "Þú hefur ekki heimild til að deila %s", + "Can’t increase permissions of %s" : "Get ekki aukið aðgangsheimildir %s", + "Files can’t be shared with delete permissions" : "Ekki er hægt að deila skrá með eyða-heimildum", + "Files can’t be shared with create permissions" : "Ekki er hægt að deila skrá með búa-til-heimildum", + "Expiration date is in the past" : "Gildistíminn er þegar runninn út", + "Can’t set expiration date more than %s days in the future" : "Ekki er hægt að setja lokadagsetningu meira en %s daga fram í tímann", + "%1$s shared »%2$s« with you" : "%1$s deildi »%2$s« með þér", + "%1$s shared »%2$s« with you." : "%1$s deildi »%2$s« með þér.", + "Click the button below to open it." : "Smelltu á hnappinn hér fyrir neðan til að opna það.", + "The requested share does not exist anymore" : "Umbeðin sameign er ekki lengur til", + "Could not find category \"%s\"" : "Fann ekki flokkinn \"%s\"", + "Sunday" : "Sunnudagur", + "Monday" : "Mánudagur", + "Tuesday" : "Þriðjudagur", + "Wednesday" : "Miðvikudagur", + "Thursday" : "Fimmtudagur", + "Friday" : "Föstudagur", + "Saturday" : "Laugardagur", + "Sun." : "Sun.", + "Mon." : "Mán.", + "Tue." : "Þri.", + "Wed." : "Mið.", + "Thu." : "Fim.", + "Fri." : "Fös.", + "Sat." : "Lau.", + "Su" : "Su", + "Mo" : "Má", + "Tu" : "Þr", + "We" : "Mi", + "Th" : "Fi", + "Fr" : "Fö", + "Sa" : "La", + "January" : "Janúar", + "February" : "Febrúar", + "March" : "Mars", + "April" : "Apríl", + "May" : "Maí", + "June" : "Júní", + "July" : "Júlí", + "August" : "Ágúst", + "September" : "September", + "October" : "Október", + "November" : "Nóvember", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maí.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Ágú.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nóv.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", og \"_.@-'\"", + "A valid username must be provided" : "Skráðu inn gilt notandanafn", + "Username contains whitespace at the beginning or at the end" : "Notandanafnið inniheldur orðabil í upphafi eða enda", + "Username must not consist of dots only" : "Notandanafn má ekki einungis samanstanda af punktum", + "Username is invalid because files already exist for this user" : "Notandanafnið er ógilt vegna þess að þegar eru fyrir hendi skrár sem tilheyra þessum notanda", + "A valid password must be provided" : "Skráðu inn gilt lykilorð", + "The username is already being used" : "Notandanafnið er þegar í notkun", + "Could not create user" : "Gat ekki búið til notanda", + "User disabled" : "Notandi óvirkur", + "Login canceled by app" : "Forrit hætti við innskráningu", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Ekki var hægt að setja upp \"%1$s\" forritið þar sem eftirfarandi kerfiskröfur eru ekki uppfylltar: %2$s", + "a safe home for all your data" : "öruggur staður fyrir öll gögnin þín", + "File is currently busy, please try again later" : "Skráin er upptekin í augnablikinu, reyndu aftur síðar", + "Can't read file" : "Get ekki lesið skrána", + "Application is not enabled" : "Forrit ekki virkt", + "Authentication error" : "Villa við auðkenningu", + "Token expired. Please reload page." : "Kenniteikn er útrunnið. Þú ættir að hlaða síðunni aftur inn.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Engir reklar fyrir gagnagrunn eru uppsettir (sqlite, mysql eða postgresql).", + "Cannot write into \"config\" directory" : "Get ekki skrifað í \"config\" möppuna", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna. Sjá %s", + "Cannot write into \"apps\" directory" : "Get ekki skrifað í \"apps\" möppuna", + "Cannot create \"data\" directory" : "Get ekki búið til \"data\" möppu", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Sjá %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Heimildir er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Sjá %s", + "Setting locale to %s failed" : "Mistókst að setja upp staðfærsluna %s", + "Please install one of these locales on your system and restart your webserver." : "Settu upp eina af þessum staðfærslum og endurræstu vefþjóninn.", + "PHP module %s not installed." : "PHP-einingin %s er ekki uppsett.", + "Please ask your server administrator to install the module." : "Biddu kerfisstjórann þinn um að setja eininguna upp.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-stillingin \"%s\" er ekki sett á \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ef þessi stilling er löguð í php.ini mun Nextcloud keyra aftur", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er stillt á \"%s\" í stað gildisins \"0\" eins og vænst var", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Til að laga þetta vandamál ættirðu að setja mbstring.func_overload sem 0 í php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Krafist er libxml2 2.7.0 hið minnsta. Núna er %s uppsett.", + "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP virðist vera sett upp to fjarlægja innantextablokkir (inline doc blocks). Þetta mun gera ýmis kjarnaforrit óaðgengileg.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Þessu veldur væntanlega biðminni/hraðall á borð við Zend OPcache eða eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Búið er að setja upp PHP-einingar, en eru þær ennþá taldar upp eins og þær vanti?", + "Please ask your server administrator to restart the web server." : "Biddu kerfisstjórann þinn um að endurræsa vefþjóninn.", + "PostgreSQL >= 9 required" : "Krefst PostgreSQL >= 9", + "Please upgrade your database version" : "Uppfærðu útgáfu gagnagrunnsins", + "Your data directory is readable by other users" : "Gagnamappn þín er lesanleg fyrir aðra notendur", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endilega breyttu heimildunum í 0770 svo að aðrir notendur geti ekki listað upp innihald hennar.", + "Your data directory must be an absolute path" : "Gagnamappan þín verður að vera með algilda slóð", + "Check the value of \"datadirectory\" in your configuration" : "Athugaðu gildi \"datadirectory\" í uppsetningunni þinni", + "Your data directory is invalid" : "Gagnamappan þín er ógild", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Gakktu úr skugga um að til staðar sé skrá með heitinu \".ocdata\" í rót gagnageymslunnar.", + "Action \"%s\" not supported or implemented." : "Aðgerðin \"%s\" er ekki studd eða útfærð.", + "Authentication failed, wrong token or provider ID given" : "Auðkenning mistókst, uppgefið rangt teikn eða auðkenni þjónustuveitu", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Breytur vantar til að geta lokið beiðninni. Breytur sem vantar: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Auðkennið \"%1$s\" er þegar notað af skýjasambandsveitunni \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Skýjasambandsveita með auðkennið \"%s\" er ekki til.", + "Could not obtain lock type %d on \"%s\"." : "Gat ekki fengið læsingu af gerðinni %d á \"%s\".", + "Storage unauthorized. %s" : "Gagnageymsla ekki auðkennd. %s", + "Storage incomplete configuration. %s" : "Ófullgerð uppsetning gagnageymslu. %s", + "Storage connection error. %s" : "Villa í tengingu við gagnageymslu. %s", + "Storage is temporarily not available" : "Gagnageymsla ekki tiltæk í augnablikinu", + "Storage connection timeout. %s" : "Gagnageymsla féll á tíma. %s", + "Following databases are supported: %s" : "Eftirfarandi gagnagrunnar eru studdir: %s", + "Following platforms are supported: %s" : "Eftirfarandi stýrikerfi eru studd: %s", + "Overview" : "Yfirlit", + "Basic settings" : "Grunnstillingar", + "Sharing" : "Deiling", + "Security" : "Öryggi", + "Groupware" : "Hópvinnukerfi", + "Personal info" : "Persónulegar upplýsingar", + "Mobile & desktop" : "Farsímar og borðtölvur", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í forritamöppuna með því að gera forritabúðina óvirka í stillingaskránni. Sjá %s" +}, +"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/is.json b/docker/overlays/nextcloud/html/lib/l10n/is.json new file mode 100644 index 0000000..fa052d1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/is.json @@ -0,0 +1,229 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Get ekki skrifað í \"config\" möppuna!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni.", + "See %s" : "Skoðaðu %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni. Skoðaðu %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Skrám forritsins %$1s var ekki rétt skipt út. Gakktu úr skugga um að þetta sé útgáfa sem sé samhæfð útgáfu vefþjónsins.", + "Sample configuration detected" : "Fann sýnisuppsetningu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Komið hefur í ljós að sýniuppsetningin var afrituð. Þetta getur skemmt uppsetninguna og er ekki stutt. Endilega lestu hjálparskjölin áður en þú gerir breytingar á config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "Education Edition" : "Kennsluútgáfa", + "Enterprise bundle" : "Fyrirtækjavöndull", + "Groupware bundle" : "Hópvinnsluvöndull", + "Social sharing bundle" : "Deilivöndull fyrir samfélagsmiðla", + "PHP %s or higher is required." : "Krafist er PHP %s eða hærra.", + "PHP with a version lower than %s is required." : "Krafist er PHP útgáfu %s eða lægri.", + "%sbit or higher PHP required." : "Krafist er PHP %sbita eða hærra.", + "The command line tool %s could not be found" : "Skipanalínutólið \"%s\" fannst ekki", + "The library %s is not available." : "Aðgerðasafnið %s er ekki tiltækt.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu hærri en %2$s - tiltæk útgáfa er %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu lægri en %2$s - tiltæk útgáfa er %3$s.", + "Server version %s or higher is required." : "Krafist er þjóns af útgáfu %s eða hærra.", + "Server version %s or lower is required." : "Krafist er þjóns af útgáfu %s eða lægri.", + "Logged in user must be an admin or sub admin" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi", + "Logged in user must be an admin" : "Innskráður notandi verður að vera stjórnandi", + "Wiping of device %s has started" : "Útþurrkun af tækinu %s er byrjuð", + "Wiping of device »%s« has started" : "Útþurrkun af tækinu »%s« er byrjuð", + "»%s« started remote wipe" : "»%s« byrjaði fjartengda útþurrkun", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Tækið eða forritið »%s« hefur hafið ferli til að þurrka út fjartengt. Þú munt fá annan tölvupóst þegar ferlinu er lokið", + "Wiping of device %s has finished" : "Útþurrkun af tækinu %s er lokið", + "Wiping of device »%s« has finished" : "Útþurrkun af tækinu »%s« er lokið", + "»%s« finished remote wipe" : "»%s« lauk fjartengdri útþurrkun", + "Device or application »%s« has finished the remote wipe process." : "Tækið eða forritið »%s« hefur lokið fjartengda útþurrkunarferlinu.", + "Remote wipe started" : "Fjartengd útþurrkun er byrjuð", + "A remote wipe was started on device %s" : "Fjartengd útþurrkun var hafin á tækinu %s", + "Remote wipe finished" : "Fjartengdri útþurrkun er lokið", + "The remote wipe on %s has finished" : "Fjartengd útþurrkun á %s er lokið", + "Authentication" : "Auðkenning", + "Unknown filetype" : "Óþekkt skráategund", + "Invalid image" : "Ógild mynd", + "Avatar image is not square" : "Auðkennismynd er ekki ferningslaga", + "today" : "í dag", + "tomorrow" : "á morgun", + "yesterday" : "í gær", + "_in %n day_::_in %n days_" : ["eftir %n dag","eftir %n daga"], + "_%n day ago_::_%n days ago_" : ["fyrir %n degi síðan","fyrir %n dögum síðan"], + "next month" : "í næsta mánuði", + "last month" : "í síðasta mánuði", + "_in %n month_::_in %n months_" : ["eftir %n mánuð","eftir %n mánuði"], + "_%n month ago_::_%n months ago_" : ["fyrir %n mánuði","fyrir %n mánuðum"], + "next year" : "á næsta ári", + "last year" : "síðasta ári", + "_in %n year_::_in %n years_" : ["eftir %n ár","eftir %n ár"], + "_%n year ago_::_%n years ago_" : ["fyrir %n degi síðan","fyrir %n árum síðan"], + "_in %n hour_::_in %n hours_" : ["eftir %n klukkustund","eftir %n klukkustundir"], + "_%n hour ago_::_%n hours ago_" : ["fyrir %n klukkustund síðan","fyrir %n klukkustundum síðan"], + "_in %n minute_::_in %n minutes_" : ["eftir %n mínútu","eftir %n mínútur"], + "_%n minute ago_::_%n minutes ago_" : ["fyrir %n mínútu síðan","fyrir %n mínútum síðan"], + "in a few seconds" : "eftir örfáar sekúndur", + "seconds ago" : "sekúndum síðan", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Eining með auðkenni: %s er ekki til. Virkjaðu hana í forritastillingum eða hafðu samband við kerfisstjóra.", + "File name is a reserved word" : "Skráarheiti er þegar frátekið orð", + "File name contains at least one invalid character" : "Skráarheitið inniheldur að minnsta kosti einn ógildan staf", + "File name is too long" : "Skráarheiti er of langt", + "Dot files are not allowed" : "Skrár með punkti eru ekki leyfðar", + "Empty filename is not allowed" : "Autt skráarheiti er ekki leyft.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Ekki er hægt að setja upp \"%s\" forritið vegna þess að ekki var hægt að lesa appinfo-skrána.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Ekki var hægt að setja upp forritið \"%s\" vegna þess að það er ekki samhæft þessari útgáfu vefþjónsins.", + "__language_name__" : "Íslenska", + "This is an automatically sent email, please do not reply." : "Þetta er sjálfvirk tölvupóstsending, ekki svara þessu.", + "Help" : "Hjálp", + "Apps" : "Forrit", + "Settings" : "Stillingar", + "Log out" : "Skrá út", + "Users" : "Notendur", + "Unknown user" : "Óþekktur notandi", + "Additional settings" : "Valfrjálsar stillingar", + "%s enter the database username and name." : "%s settu inn notandanafn og nafn á gagnagrunni.", + "%s enter the database username." : "%s settu inn notandanafn í gagnagrunni.", + "%s enter the database name." : "%s settu inn nafn á gagnagrunni.", + "%s you may not use dots in the database name" : "%s þú mátt ekki nota punkta í nafni á gagnagrunni", + "You need to enter details of an existing account." : "Þú verður að setja inn auðkenni fyrirliggjandi notandaaðgangs.", + "Oracle connection could not be established" : "Ekki tókst að koma tengingu á við Oracle", + "Oracle username and/or password not valid" : "Notandanafn eða lykilorð Oracle er ekki gilt", + "PostgreSQL username and/or password not valid" : "Notandanafn eða lykilorð PostgreSQL er ekki gilt", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X er ekki stutt og %s mun ekki vinna eðlilega á þessu stýrikerfi. Notaðu þetta því á þína eigin ábyrgð! ", + "For the best results, please consider using a GNU/Linux server instead." : "Fyrir bestu útkomu ættirðu að íhuga að nota GNU/Linux þjón í staðinn.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Það lítur út eins og þessi %s uppsetning sé að keyra á 32-bita PHP umhverfi og að open_basedir hafi verið stillt í php.ini. Þetta mun valda vandamálum með skrár stærri en 4 GB og er stranglega mælt gegn því að þetta sé gert.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjarlægðu stillinguna open_basedir úr php.ini eða skiptu yfir í 64-bita PHP.", + "Set an admin username." : "Stilltu notandanafn kerfisstjóra.", + "Set an admin password." : "Stilltu lykilorð kerfisstjóra.", + "Can't create or write into the data directory %s" : "Gat ekki búið til eða skrifað í gagnamöppuna %s", + "Invalid Federated Cloud ID" : "Ógilt skýjasambandsauðkenni (Federated Cloud ID)", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Deilingarbakendinn %s verður að vera settur upp fyrir viðmótið OCP\\Share_Backend", + "Sharing backend %s not found" : "Deilingarbakendinn %s fannst ekki", + "Sharing backend for %s not found" : "Deilingarbakendi fyrir %s fannst ekki", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s deildi »%2$s« með þér og vill bæta við:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s deildi »%2$s« með þér og vill bæta við", + "»%s« added a note to a file shared with you" : "»%s« bætti minnispunkti við skrá sem deilt er með þér", + "Open »%s«" : "Opna »%s«", + "%1$s via %2$s" : "%1$s með %2$s", + "You are not allowed to share %s" : "Þú hefur ekki heimild til að deila %s", + "Can’t increase permissions of %s" : "Get ekki aukið aðgangsheimildir %s", + "Files can’t be shared with delete permissions" : "Ekki er hægt að deila skrá með eyða-heimildum", + "Files can’t be shared with create permissions" : "Ekki er hægt að deila skrá með búa-til-heimildum", + "Expiration date is in the past" : "Gildistíminn er þegar runninn út", + "Can’t set expiration date more than %s days in the future" : "Ekki er hægt að setja lokadagsetningu meira en %s daga fram í tímann", + "%1$s shared »%2$s« with you" : "%1$s deildi »%2$s« með þér", + "%1$s shared »%2$s« with you." : "%1$s deildi »%2$s« með þér.", + "Click the button below to open it." : "Smelltu á hnappinn hér fyrir neðan til að opna það.", + "The requested share does not exist anymore" : "Umbeðin sameign er ekki lengur til", + "Could not find category \"%s\"" : "Fann ekki flokkinn \"%s\"", + "Sunday" : "Sunnudagur", + "Monday" : "Mánudagur", + "Tuesday" : "Þriðjudagur", + "Wednesday" : "Miðvikudagur", + "Thursday" : "Fimmtudagur", + "Friday" : "Föstudagur", + "Saturday" : "Laugardagur", + "Sun." : "Sun.", + "Mon." : "Mán.", + "Tue." : "Þri.", + "Wed." : "Mið.", + "Thu." : "Fim.", + "Fri." : "Fös.", + "Sat." : "Lau.", + "Su" : "Su", + "Mo" : "Má", + "Tu" : "Þr", + "We" : "Mi", + "Th" : "Fi", + "Fr" : "Fö", + "Sa" : "La", + "January" : "Janúar", + "February" : "Febrúar", + "March" : "Mars", + "April" : "Apríl", + "May" : "Maí", + "June" : "Júní", + "July" : "Júlí", + "August" : "Ágúst", + "September" : "September", + "October" : "Október", + "November" : "Nóvember", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maí.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Ágú.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nóv.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", og \"_.@-'\"", + "A valid username must be provided" : "Skráðu inn gilt notandanafn", + "Username contains whitespace at the beginning or at the end" : "Notandanafnið inniheldur orðabil í upphafi eða enda", + "Username must not consist of dots only" : "Notandanafn má ekki einungis samanstanda af punktum", + "Username is invalid because files already exist for this user" : "Notandanafnið er ógilt vegna þess að þegar eru fyrir hendi skrár sem tilheyra þessum notanda", + "A valid password must be provided" : "Skráðu inn gilt lykilorð", + "The username is already being used" : "Notandanafnið er þegar í notkun", + "Could not create user" : "Gat ekki búið til notanda", + "User disabled" : "Notandi óvirkur", + "Login canceled by app" : "Forrit hætti við innskráningu", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Ekki var hægt að setja upp \"%1$s\" forritið þar sem eftirfarandi kerfiskröfur eru ekki uppfylltar: %2$s", + "a safe home for all your data" : "öruggur staður fyrir öll gögnin þín", + "File is currently busy, please try again later" : "Skráin er upptekin í augnablikinu, reyndu aftur síðar", + "Can't read file" : "Get ekki lesið skrána", + "Application is not enabled" : "Forrit ekki virkt", + "Authentication error" : "Villa við auðkenningu", + "Token expired. Please reload page." : "Kenniteikn er útrunnið. Þú ættir að hlaða síðunni aftur inn.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Engir reklar fyrir gagnagrunn eru uppsettir (sqlite, mysql eða postgresql).", + "Cannot write into \"config\" directory" : "Get ekki skrifað í \"config\" möppuna", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna. Sjá %s", + "Cannot write into \"apps\" directory" : "Get ekki skrifað í \"apps\" möppuna", + "Cannot create \"data\" directory" : "Get ekki búið til \"data\" möppu", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Sjá %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Heimildir er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Sjá %s", + "Setting locale to %s failed" : "Mistókst að setja upp staðfærsluna %s", + "Please install one of these locales on your system and restart your webserver." : "Settu upp eina af þessum staðfærslum og endurræstu vefþjóninn.", + "PHP module %s not installed." : "PHP-einingin %s er ekki uppsett.", + "Please ask your server administrator to install the module." : "Biddu kerfisstjórann þinn um að setja eininguna upp.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-stillingin \"%s\" er ekki sett á \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ef þessi stilling er löguð í php.ini mun Nextcloud keyra aftur", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er stillt á \"%s\" í stað gildisins \"0\" eins og vænst var", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Til að laga þetta vandamál ættirðu að setja mbstring.func_overload sem 0 í php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Krafist er libxml2 2.7.0 hið minnsta. Núna er %s uppsett.", + "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP virðist vera sett upp to fjarlægja innantextablokkir (inline doc blocks). Þetta mun gera ýmis kjarnaforrit óaðgengileg.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Þessu veldur væntanlega biðminni/hraðall á borð við Zend OPcache eða eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Búið er að setja upp PHP-einingar, en eru þær ennþá taldar upp eins og þær vanti?", + "Please ask your server administrator to restart the web server." : "Biddu kerfisstjórann þinn um að endurræsa vefþjóninn.", + "PostgreSQL >= 9 required" : "Krefst PostgreSQL >= 9", + "Please upgrade your database version" : "Uppfærðu útgáfu gagnagrunnsins", + "Your data directory is readable by other users" : "Gagnamappn þín er lesanleg fyrir aðra notendur", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endilega breyttu heimildunum í 0770 svo að aðrir notendur geti ekki listað upp innihald hennar.", + "Your data directory must be an absolute path" : "Gagnamappan þín verður að vera með algilda slóð", + "Check the value of \"datadirectory\" in your configuration" : "Athugaðu gildi \"datadirectory\" í uppsetningunni þinni", + "Your data directory is invalid" : "Gagnamappan þín er ógild", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Gakktu úr skugga um að til staðar sé skrá með heitinu \".ocdata\" í rót gagnageymslunnar.", + "Action \"%s\" not supported or implemented." : "Aðgerðin \"%s\" er ekki studd eða útfærð.", + "Authentication failed, wrong token or provider ID given" : "Auðkenning mistókst, uppgefið rangt teikn eða auðkenni þjónustuveitu", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Breytur vantar til að geta lokið beiðninni. Breytur sem vantar: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Auðkennið \"%1$s\" er þegar notað af skýjasambandsveitunni \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Skýjasambandsveita með auðkennið \"%s\" er ekki til.", + "Could not obtain lock type %d on \"%s\"." : "Gat ekki fengið læsingu af gerðinni %d á \"%s\".", + "Storage unauthorized. %s" : "Gagnageymsla ekki auðkennd. %s", + "Storage incomplete configuration. %s" : "Ófullgerð uppsetning gagnageymslu. %s", + "Storage connection error. %s" : "Villa í tengingu við gagnageymslu. %s", + "Storage is temporarily not available" : "Gagnageymsla ekki tiltæk í augnablikinu", + "Storage connection timeout. %s" : "Gagnageymsla féll á tíma. %s", + "Following databases are supported: %s" : "Eftirfarandi gagnagrunnar eru studdir: %s", + "Following platforms are supported: %s" : "Eftirfarandi stýrikerfi eru studd: %s", + "Overview" : "Yfirlit", + "Basic settings" : "Grunnstillingar", + "Sharing" : "Deiling", + "Security" : "Öryggi", + "Groupware" : "Hópvinnukerfi", + "Personal info" : "Persónulegar upplýsingar", + "Mobile & desktop" : "Farsímar og borðtölvur", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í forritamöppuna með því að gera forritabúðina óvirka í stillingaskránni. Sjá %s" +},"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/it.js b/docker/overlays/nextcloud/html/lib/l10n/it.js new file mode 100644 index 0000000..8b352f6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/it.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Impossibile scrivere nella cartella \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"config\"", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true.", + "See %s" : "Vedi %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"config\".", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true. Vedi %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "I file dell'applicazione %1$s non sono stati sostituiti correttamente. Assicurati che sia una versione compatibile con il server.", + "Sample configuration detected" : "Configurazione di esempio rilevata", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "È stato rilevato che la configurazione di esempio è stata copiata. Ciò può compromettere la tua installazione e non è supportato. Leggi la documentazione prima di modificare il file config.php", + "Other activities" : "Altre attività", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edizione didattica", + "Enterprise bundle" : "Pacchetto Enterprise", + "Groupware bundle" : "Pacchetto Groupware", + "Hub bundle" : "Pacchetto Hub", + "Social sharing bundle" : "Pacchetto Social sharing", + "PHP %s or higher is required." : "Richiesto PHP %s o superiore", + "PHP with a version lower than %s is required." : "Richiesta una versione di PHP minore di %s.", + "%sbit or higher PHP required." : "Richiesto PHP %sbit o superiore.", + "The following architectures are supported: %s" : "Sono supportate le seguenti architetture: %s", + "The following databases are supported: %s" : "I seguenti database sono supportati: %s", + "The command line tool %s could not be found" : "Lo strumento da riga di comando %s non è stato trovato", + "The library %s is not available." : "La libreria %s non è disponibile.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Richiesta una versione della libreria %1$s maggiore di %2$s - versione disponibile %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Richiesta una versione della libreria %1$s minore di %2$s - versione disponibile %3$s.", + "The following platforms are supported: %s" : "Le seguenti piattaforme sono supportate: %s", + "Server version %s or higher is required." : "È richiesta la versione %s o successiva.", + "Server version %s or lower is required." : "È richiesta la versione %s o precedente.", + "Logged in user must be an admin or sub admin" : "L'utente che ha eseguito l'accesso deve essere un amministratore o sub-amministratore", + "Logged in user must be an admin" : "L'utente che ha eseguito l'accesso deve essere un amministratore ", + "Wiping of device %s has started" : "La cancellazione del dispositivo %s è iniziata", + "Wiping of device »%s« has started" : "La cancellazione del dispositivo «%s» è iniziata", + "»%s« started remote wipe" : "«%s» ha iniziato la cancellazione remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Il dispositivo o l'applicazione «%s» ha iniziato il processo di cancellazione remota. Riceverai un'altra email al termine del processo ", + "Wiping of device %s has finished" : "La cancellazione del dispositivo %s è terminata", + "Wiping of device »%s« has finished" : "La cancellazione del dispositivo «%s» è terminata", + "»%s« finished remote wipe" : "«%s» ha terminato la cancellazione remota", + "Device or application »%s« has finished the remote wipe process." : "Il dispositivo o l'applicazione «%s» ha terminato il processo di cancellazione remota.", + "Remote wipe started" : "Cancellazione remota avviata", + "A remote wipe was started on device %s" : "Una cancellazione remota è stata avviata sul dispositivo %s", + "Remote wipe finished" : "Cancellazione remota terminata", + "The remote wipe on %s has finished" : "La cancellazione remota su %s è terminata", + "Authentication" : "Autenticazione", + "Unknown filetype" : "Tipo di file sconosciuto", + "Invalid image" : "Immagine non valida", + "Avatar image is not square" : "L'immagine personale non è quadrata", + "today" : "oggi", + "tomorrow" : "domani", + "yesterday" : "ieri", + "_in %n day_::_in %n days_" : ["tra %n giorno","tra %n giorni"], + "_%n day ago_::_%n days ago_" : ["%d giorno fa","%n giorni fa"], + "next month" : "il prossimo mese", + "last month" : "mese scorso", + "_in %n month_::_in %n months_" : ["tra %n mese","tra %n mesi"], + "_%n month ago_::_%n months ago_" : ["%n mese fa","%n mesi fa"], + "next year" : "il prossimo anno", + "last year" : "anno scorso", + "_in %n year_::_in %n years_" : ["tra %n anno","tra %n anni"], + "_%n year ago_::_%n years ago_" : ["%n anno fa","%n anni fa"], + "_in %n hour_::_in %n hours_" : ["tra %n ora","tra %n ore"], + "_%n hour ago_::_%n hours ago_" : ["%n ora fa","%n ore fa"], + "_in %n minute_::_in %n minutes_" : ["tra %n minuto","tra %n minuti"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuto fa","%n minuti fa"], + "in a few seconds" : "tra pochi secondi", + "seconds ago" : "secondi fa", + "Empty file" : "File vuoto", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Il modulo con ID: %s non esiste. Abilitalo nelle impostazioni delle applicazioni o contatta il tuo amministratore.", + "File name is a reserved word" : "Il nome del file è una parola riservata", + "File name contains at least one invalid character" : "Il nome del file contiene almeno un carattere non valido", + "File name is too long" : "Il nome del file è troppo lungo", + "Dot files are not allowed" : "I file con un punto iniziale non sono consentiti", + "Empty filename is not allowed" : "Un nome di file vuoto non è consentito", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'applicazione \"%s\" non può essere installata poiché il file appinfo non può essere letto.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'applicazione \"%s\" non può essere installata perché non è compatibile con questa versione del server.", + "__language_name__" : "Italiano", + "This is an automatically sent email, please do not reply." : "Questo è un messaggio di posta inviato automaticamente, non rispondere.", + "Help" : "Aiuto", + "Apps" : "Applicazioni", + "Settings" : "Impostazioni", + "Log out" : "Esci", + "Users" : "Utenti", + "Unknown user" : "Utente sconosciuto", + "Additional settings" : "Impostazioni aggiuntive", + "%s enter the database username and name." : "%s digita il nome utente e il nome del database.", + "%s enter the database username." : "%s digita il nome utente del database.", + "%s enter the database name." : "%s digita il nome del database.", + "%s you may not use dots in the database name" : "%s non dovresti utilizzare punti nel nome del database", + "MySQL username and/or password not valid" : "Nome utente e/o password di MySQL non validi", + "You need to enter details of an existing account." : "Devi inserire i dettagli di un account esistente.", + "Oracle connection could not be established" : "La connessione a Oracle non può essere stabilita", + "Oracle username and/or password not valid" : "Nome utente e/o password di Oracle non validi", + "PostgreSQL username and/or password not valid" : "Nome utente e/o password di PostgreSQL non validi", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X non è supportato e %s non funzionerà correttamente su questa piattaforma. Usalo a tuo rischio!", + "For the best results, please consider using a GNU/Linux server instead." : "Per avere il risultato migliore, prendi in considerazione l'utilizzo di un server GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Sembra che questa istanza di %s sia in esecuzione in un ambiente PHP a 32 bit e che open_basedir sia stata configurata in php.ini. Ciò comporterà problemi con i file più grandi di 4 GB ed è altamente sconsigliato.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Rimuovi l'impostazione di open_basedir nel tuo php.ini o passa alla versione a 64 bit di PHP.", + "Set an admin username." : "Imposta un nome utente di amministrazione.", + "Set an admin password." : "Imposta una password di amministrazione.", + "Can't create or write into the data directory %s" : "Impossibile creare o scrivere nella cartella dei dati %s", + "Invalid Federated Cloud ID" : "ID di cloud federata non valido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Il motore di condivisione %s deve implementare l'interfaccia OCP\\Share_Backend", + "Sharing backend %s not found" : "Motore di condivisione %s non trovato", + "Sharing backend for %s not found" : "Motore di condivisione di %s non trovato", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s ha condiviso «%2$s» con te e vuole aggiungere:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s ha condiviso «%2$s» con te e vuole aggiungere", + "»%s« added a note to a file shared with you" : "«%s» ha aggiunto una nota a un file condiviso con te", + "Open »%s«" : "Apri «%s»", + "%1$s via %2$s" : "%1$s tramite %2$s", + "You are not allowed to share %s" : "Non ti è consentito condividere %s", + "Can’t increase permissions of %s" : "Impossibile aumentare i permessi di %s", + "Files can’t be shared with delete permissions" : "I file non possono essere condivisi con permessi di eliminazione", + "Files can’t be shared with create permissions" : "I file non possono essere condivisi con permessi di creazione", + "Expiration date is in the past" : "La data di scadenza è nel passato", + "Can’t set expiration date more than %s days in the future" : "Impossibile impostare la data di scadenza a più di %s giorni nel futuro", + "%1$s shared »%2$s« with you" : "%1$s ha condiviso «%2$s» con te", + "%1$s shared »%2$s« with you." : "%1$s ha condiviso «%2$s» con te.", + "Click the button below to open it." : "Fai clic sul pulsante sotto per aprirlo.", + "The requested share does not exist anymore" : "La condivisione richiesta non esiste più", + "Could not find category \"%s\"" : "Impossibile trovare la categoria \"%s\"", + "Sunday" : "Domenica", + "Monday" : "Lunedì", + "Tuesday" : "Martedì", + "Wednesday" : "Mercoledì", + "Thursday" : "Giovedì", + "Friday" : "Venerdì", + "Saturday" : "Sabato", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Gio.", + "Fri." : "Ven.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Gi", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Gennaio", + "February" : "Febbraio", + "March" : "Marzo", + "April" : "Aprile", + "May" : "Maggio", + "June" : "Giugno", + "July" : "Luglio", + "August" : "Agosto", + "September" : "Settembre", + "October" : "Ottobre", + "November" : "Novembre", + "December" : "Dicembre", + "Jan." : "Gen.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mag.", + "Jun." : "Giu.", + "Jul." : "Lug.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Ott.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo i seguenti caratteri sono consentiti in un nome utente: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Deve essere fornito un nome utente valido", + "Username contains whitespace at the beginning or at the end" : "Il nome utente contiene spazi all'inizio o alla fine", + "Username must not consist of dots only" : "Il nome utente non può consistere di soli punti", + "Username is invalid because files already exist for this user" : "Il nome utente non è valido poiché esiste già per questo utente", + "A valid password must be provided" : "Deve essere fornita una password valida", + "The username is already being used" : "Il nome utente è già utilizzato", + "Could not create user" : "Impossibile creare l'utente", + "User disabled" : "Utente disabilitato", + "Login canceled by app" : "Accesso annullato dall'applicazione", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'applicazione \"%1$s\" non può essere installata poiché le seguenti dipendenze non sono soddisfatte: %2$s", + "a safe home for all your data" : "un posto sicuro per tutti i tuoi dati", + "File is currently busy, please try again later" : "Il file è attualmente occupato, riprova più tardi", + "Can't read file" : "Impossibile leggere il file", + "Application is not enabled" : "L'applicazione non è abilitata", + "Authentication error" : "Errore di autenticazione", + "Token expired. Please reload page." : "Token scaduto. Ricarica la pagina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nessun driver di database (sqlite, mysql o postgresql) installato", + "Cannot write into \"config\" directory" : "Impossibile scrivere nella cartella \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella di configurazione. Vedi %s", + "Cannot write into \"apps\" directory" : "Impossibile scrivere nella cartella \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Questo può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"apps\" o disabilitando il negozio di applicazioni nel file di configurazione.", + "Cannot create \"data\" directory" : "Impossibile creare la cartella \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella radice. Vedi %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "I permessi possono essere normalmente corretti fornendo al server web accesso in scrittura alla cartella radice. Vedi %s.", + "Setting locale to %s failed" : "L'impostazione della localizzazione a %s non è riuscita", + "Please install one of these locales on your system and restart your webserver." : "Installa una delle seguenti localizzazioni sul tuo sistema e riavvia il server web.", + "PHP module %s not installed." : "Il modulo PHP %s non è installato.", + "Please ask your server administrator to install the module." : "Chiedi all'amministratore del tuo server di installare il modulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "L'impostazione \"%s\" di PHP non è configurata a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Per eseguire nuovamente Nextcloud, modificare questa impostazione nel file php.ini", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload è impostata a \"%s\" invece del valore atteso \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Per correggere questo problema, imposta mbstring.func_overload a 0 nel tuo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "È richiesta almeno la versione 2.7.0 di libxml2. Quella attualmente installata è la %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Per risolvere questo problema, aggiorna la tua versione di libxml2 e riavvia il server web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Sembra che PHP sia configurato per rimuovere i blocchi di documentazione in linea. Ciò renderà inaccessibili diverse applicazioni principali.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ciò è causato probabilmente da una cache/acceleratore come Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Sono stati installati moduli PHP, ma sono elencati ancora come mancanti?", + "Please ask your server administrator to restart the web server." : "Chiedi all'amministratore di riavviare il server web.", + "PostgreSQL >= 9 required" : "Richiesto PostgreSQL >= 9", + "Please upgrade your database version" : "Aggiorna la versione del tuo database", + "Your data directory is readable by other users" : "La cartella dei dati è leggibile dagli altri utenti", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Modifica i permessi in 0770 in modo tale che la cartella non sia leggibile dagli altri utenti.", + "Your data directory must be an absolute path" : "La cartella dei dati deve essere un percorso assoluto", + "Check the value of \"datadirectory\" in your configuration" : "Controlla il valore di \"datadirectory\" nella tua configurazione", + "Your data directory is invalid" : "La cartella dei dati non è valida", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assicurati che ci sia un file \".ocdata\" nella radice della cartella data.", + "Action \"%s\" not supported or implemented." : "Azione \"%s\" non supportata o implementata.", + "Authentication failed, wrong token or provider ID given" : "Autenticazione non riuscita, token o ID fornitore errato ", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametri mancanti per completare la richiesta. Parametri mancanti: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" già utilizzato dal fornitore della federazione cloud \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Il fornitore della federazione cloud con ID: \"%s\" non esiste.", + "Could not obtain lock type %d on \"%s\"." : "Impossibile ottenere il blocco di tipo %d su \"%s\".", + "Storage unauthorized. %s" : "Archiviazione non autorizzata. %s", + "Storage incomplete configuration. %s" : "Configurazione dell'archiviazione incompleta.%s", + "Storage connection error. %s" : "Errore di connessione all'archiviazione. %s", + "Storage is temporarily not available" : "L'archiviazione è temporaneamente non disponibile", + "Storage connection timeout. %s" : "Timeout di connessione all'archiviazione. %s", + "Following databases are supported: %s" : "I seguenti database sono supportati: %s", + "Following platforms are supported: %s" : "Sono supportate le seguenti piattaforme: %s", + "Overview" : "Riepilogo", + "Basic settings" : "Impostazioni di base", + "Sharing" : "Condivisione", + "Security" : "Sicurezza", + "Groupware" : "Groupware", + "Personal info" : "Informazioni personali", + "Mobile & desktop" : "Mobile e desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella delle applicazioni o disabilitando il negozio di applicazioni nel file di configurazione. Vedi %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/it.json b/docker/overlays/nextcloud/html/lib/l10n/it.json new file mode 100644 index 0000000..5294385 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/it.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Impossibile scrivere nella cartella \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"config\"", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true.", + "See %s" : "Vedi %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"config\".", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true. Vedi %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "I file dell'applicazione %1$s non sono stati sostituiti correttamente. Assicurati che sia una versione compatibile con il server.", + "Sample configuration detected" : "Configurazione di esempio rilevata", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "È stato rilevato che la configurazione di esempio è stata copiata. Ciò può compromettere la tua installazione e non è supportato. Leggi la documentazione prima di modificare il file config.php", + "Other activities" : "Altre attività", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edizione didattica", + "Enterprise bundle" : "Pacchetto Enterprise", + "Groupware bundle" : "Pacchetto Groupware", + "Hub bundle" : "Pacchetto Hub", + "Social sharing bundle" : "Pacchetto Social sharing", + "PHP %s or higher is required." : "Richiesto PHP %s o superiore", + "PHP with a version lower than %s is required." : "Richiesta una versione di PHP minore di %s.", + "%sbit or higher PHP required." : "Richiesto PHP %sbit o superiore.", + "The following architectures are supported: %s" : "Sono supportate le seguenti architetture: %s", + "The following databases are supported: %s" : "I seguenti database sono supportati: %s", + "The command line tool %s could not be found" : "Lo strumento da riga di comando %s non è stato trovato", + "The library %s is not available." : "La libreria %s non è disponibile.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Richiesta una versione della libreria %1$s maggiore di %2$s - versione disponibile %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Richiesta una versione della libreria %1$s minore di %2$s - versione disponibile %3$s.", + "The following platforms are supported: %s" : "Le seguenti piattaforme sono supportate: %s", + "Server version %s or higher is required." : "È richiesta la versione %s o successiva.", + "Server version %s or lower is required." : "È richiesta la versione %s o precedente.", + "Logged in user must be an admin or sub admin" : "L'utente che ha eseguito l'accesso deve essere un amministratore o sub-amministratore", + "Logged in user must be an admin" : "L'utente che ha eseguito l'accesso deve essere un amministratore ", + "Wiping of device %s has started" : "La cancellazione del dispositivo %s è iniziata", + "Wiping of device »%s« has started" : "La cancellazione del dispositivo «%s» è iniziata", + "»%s« started remote wipe" : "«%s» ha iniziato la cancellazione remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Il dispositivo o l'applicazione «%s» ha iniziato il processo di cancellazione remota. Riceverai un'altra email al termine del processo ", + "Wiping of device %s has finished" : "La cancellazione del dispositivo %s è terminata", + "Wiping of device »%s« has finished" : "La cancellazione del dispositivo «%s» è terminata", + "»%s« finished remote wipe" : "«%s» ha terminato la cancellazione remota", + "Device or application »%s« has finished the remote wipe process." : "Il dispositivo o l'applicazione «%s» ha terminato il processo di cancellazione remota.", + "Remote wipe started" : "Cancellazione remota avviata", + "A remote wipe was started on device %s" : "Una cancellazione remota è stata avviata sul dispositivo %s", + "Remote wipe finished" : "Cancellazione remota terminata", + "The remote wipe on %s has finished" : "La cancellazione remota su %s è terminata", + "Authentication" : "Autenticazione", + "Unknown filetype" : "Tipo di file sconosciuto", + "Invalid image" : "Immagine non valida", + "Avatar image is not square" : "L'immagine personale non è quadrata", + "today" : "oggi", + "tomorrow" : "domani", + "yesterday" : "ieri", + "_in %n day_::_in %n days_" : ["tra %n giorno","tra %n giorni"], + "_%n day ago_::_%n days ago_" : ["%d giorno fa","%n giorni fa"], + "next month" : "il prossimo mese", + "last month" : "mese scorso", + "_in %n month_::_in %n months_" : ["tra %n mese","tra %n mesi"], + "_%n month ago_::_%n months ago_" : ["%n mese fa","%n mesi fa"], + "next year" : "il prossimo anno", + "last year" : "anno scorso", + "_in %n year_::_in %n years_" : ["tra %n anno","tra %n anni"], + "_%n year ago_::_%n years ago_" : ["%n anno fa","%n anni fa"], + "_in %n hour_::_in %n hours_" : ["tra %n ora","tra %n ore"], + "_%n hour ago_::_%n hours ago_" : ["%n ora fa","%n ore fa"], + "_in %n minute_::_in %n minutes_" : ["tra %n minuto","tra %n minuti"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuto fa","%n minuti fa"], + "in a few seconds" : "tra pochi secondi", + "seconds ago" : "secondi fa", + "Empty file" : "File vuoto", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Il modulo con ID: %s non esiste. Abilitalo nelle impostazioni delle applicazioni o contatta il tuo amministratore.", + "File name is a reserved word" : "Il nome del file è una parola riservata", + "File name contains at least one invalid character" : "Il nome del file contiene almeno un carattere non valido", + "File name is too long" : "Il nome del file è troppo lungo", + "Dot files are not allowed" : "I file con un punto iniziale non sono consentiti", + "Empty filename is not allowed" : "Un nome di file vuoto non è consentito", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'applicazione \"%s\" non può essere installata poiché il file appinfo non può essere letto.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'applicazione \"%s\" non può essere installata perché non è compatibile con questa versione del server.", + "__language_name__" : "Italiano", + "This is an automatically sent email, please do not reply." : "Questo è un messaggio di posta inviato automaticamente, non rispondere.", + "Help" : "Aiuto", + "Apps" : "Applicazioni", + "Settings" : "Impostazioni", + "Log out" : "Esci", + "Users" : "Utenti", + "Unknown user" : "Utente sconosciuto", + "Additional settings" : "Impostazioni aggiuntive", + "%s enter the database username and name." : "%s digita il nome utente e il nome del database.", + "%s enter the database username." : "%s digita il nome utente del database.", + "%s enter the database name." : "%s digita il nome del database.", + "%s you may not use dots in the database name" : "%s non dovresti utilizzare punti nel nome del database", + "MySQL username and/or password not valid" : "Nome utente e/o password di MySQL non validi", + "You need to enter details of an existing account." : "Devi inserire i dettagli di un account esistente.", + "Oracle connection could not be established" : "La connessione a Oracle non può essere stabilita", + "Oracle username and/or password not valid" : "Nome utente e/o password di Oracle non validi", + "PostgreSQL username and/or password not valid" : "Nome utente e/o password di PostgreSQL non validi", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X non è supportato e %s non funzionerà correttamente su questa piattaforma. Usalo a tuo rischio!", + "For the best results, please consider using a GNU/Linux server instead." : "Per avere il risultato migliore, prendi in considerazione l'utilizzo di un server GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Sembra che questa istanza di %s sia in esecuzione in un ambiente PHP a 32 bit e che open_basedir sia stata configurata in php.ini. Ciò comporterà problemi con i file più grandi di 4 GB ed è altamente sconsigliato.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Rimuovi l'impostazione di open_basedir nel tuo php.ini o passa alla versione a 64 bit di PHP.", + "Set an admin username." : "Imposta un nome utente di amministrazione.", + "Set an admin password." : "Imposta una password di amministrazione.", + "Can't create or write into the data directory %s" : "Impossibile creare o scrivere nella cartella dei dati %s", + "Invalid Federated Cloud ID" : "ID di cloud federata non valido", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Il motore di condivisione %s deve implementare l'interfaccia OCP\\Share_Backend", + "Sharing backend %s not found" : "Motore di condivisione %s non trovato", + "Sharing backend for %s not found" : "Motore di condivisione di %s non trovato", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s ha condiviso «%2$s» con te e vuole aggiungere:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s ha condiviso «%2$s» con te e vuole aggiungere", + "»%s« added a note to a file shared with you" : "«%s» ha aggiunto una nota a un file condiviso con te", + "Open »%s«" : "Apri «%s»", + "%1$s via %2$s" : "%1$s tramite %2$s", + "You are not allowed to share %s" : "Non ti è consentito condividere %s", + "Can’t increase permissions of %s" : "Impossibile aumentare i permessi di %s", + "Files can’t be shared with delete permissions" : "I file non possono essere condivisi con permessi di eliminazione", + "Files can’t be shared with create permissions" : "I file non possono essere condivisi con permessi di creazione", + "Expiration date is in the past" : "La data di scadenza è nel passato", + "Can’t set expiration date more than %s days in the future" : "Impossibile impostare la data di scadenza a più di %s giorni nel futuro", + "%1$s shared »%2$s« with you" : "%1$s ha condiviso «%2$s» con te", + "%1$s shared »%2$s« with you." : "%1$s ha condiviso «%2$s» con te.", + "Click the button below to open it." : "Fai clic sul pulsante sotto per aprirlo.", + "The requested share does not exist anymore" : "La condivisione richiesta non esiste più", + "Could not find category \"%s\"" : "Impossibile trovare la categoria \"%s\"", + "Sunday" : "Domenica", + "Monday" : "Lunedì", + "Tuesday" : "Martedì", + "Wednesday" : "Mercoledì", + "Thursday" : "Giovedì", + "Friday" : "Venerdì", + "Saturday" : "Sabato", + "Sun." : "Dom.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mer.", + "Thu." : "Gio.", + "Fri." : "Ven.", + "Sat." : "Sab.", + "Su" : "Do", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Me", + "Th" : "Gi", + "Fr" : "Ve", + "Sa" : "Sa", + "January" : "Gennaio", + "February" : "Febbraio", + "March" : "Marzo", + "April" : "Aprile", + "May" : "Maggio", + "June" : "Giugno", + "July" : "Luglio", + "August" : "Agosto", + "September" : "Settembre", + "October" : "Ottobre", + "November" : "Novembre", + "December" : "Dicembre", + "Jan." : "Gen.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mag.", + "Jun." : "Giu.", + "Jul." : "Lug.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Ott.", + "Nov." : "Nov.", + "Dec." : "Dic.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo i seguenti caratteri sono consentiti in un nome utente: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Deve essere fornito un nome utente valido", + "Username contains whitespace at the beginning or at the end" : "Il nome utente contiene spazi all'inizio o alla fine", + "Username must not consist of dots only" : "Il nome utente non può consistere di soli punti", + "Username is invalid because files already exist for this user" : "Il nome utente non è valido poiché esiste già per questo utente", + "A valid password must be provided" : "Deve essere fornita una password valida", + "The username is already being used" : "Il nome utente è già utilizzato", + "Could not create user" : "Impossibile creare l'utente", + "User disabled" : "Utente disabilitato", + "Login canceled by app" : "Accesso annullato dall'applicazione", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'applicazione \"%1$s\" non può essere installata poiché le seguenti dipendenze non sono soddisfatte: %2$s", + "a safe home for all your data" : "un posto sicuro per tutti i tuoi dati", + "File is currently busy, please try again later" : "Il file è attualmente occupato, riprova più tardi", + "Can't read file" : "Impossibile leggere il file", + "Application is not enabled" : "L'applicazione non è abilitata", + "Authentication error" : "Errore di autenticazione", + "Token expired. Please reload page." : "Token scaduto. Ricarica la pagina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nessun driver di database (sqlite, mysql o postgresql) installato", + "Cannot write into \"config\" directory" : "Impossibile scrivere nella cartella \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella di configurazione. Vedi %s", + "Cannot write into \"apps\" directory" : "Impossibile scrivere nella cartella \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Questo può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella \"apps\" o disabilitando il negozio di applicazioni nel file di configurazione.", + "Cannot create \"data\" directory" : "Impossibile creare la cartella \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella radice. Vedi %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "I permessi possono essere normalmente corretti fornendo al server web accesso in scrittura alla cartella radice. Vedi %s.", + "Setting locale to %s failed" : "L'impostazione della localizzazione a %s non è riuscita", + "Please install one of these locales on your system and restart your webserver." : "Installa una delle seguenti localizzazioni sul tuo sistema e riavvia il server web.", + "PHP module %s not installed." : "Il modulo PHP %s non è installato.", + "Please ask your server administrator to install the module." : "Chiedi all'amministratore del tuo server di installare il modulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "L'impostazione \"%s\" di PHP non è configurata a \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Per eseguire nuovamente Nextcloud, modificare questa impostazione nel file php.ini", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload è impostata a \"%s\" invece del valore atteso \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Per correggere questo problema, imposta mbstring.func_overload a 0 nel tuo php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "È richiesta almeno la versione 2.7.0 di libxml2. Quella attualmente installata è la %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Per risolvere questo problema, aggiorna la tua versione di libxml2 e riavvia il server web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Sembra che PHP sia configurato per rimuovere i blocchi di documentazione in linea. Ciò renderà inaccessibili diverse applicazioni principali.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ciò è causato probabilmente da una cache/acceleratore come Zend OPcache o eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Sono stati installati moduli PHP, ma sono elencati ancora come mancanti?", + "Please ask your server administrator to restart the web server." : "Chiedi all'amministratore di riavviare il server web.", + "PostgreSQL >= 9 required" : "Richiesto PostgreSQL >= 9", + "Please upgrade your database version" : "Aggiorna la versione del tuo database", + "Your data directory is readable by other users" : "La cartella dei dati è leggibile dagli altri utenti", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Modifica i permessi in 0770 in modo tale che la cartella non sia leggibile dagli altri utenti.", + "Your data directory must be an absolute path" : "La cartella dei dati deve essere un percorso assoluto", + "Check the value of \"datadirectory\" in your configuration" : "Controlla il valore di \"datadirectory\" nella tua configurazione", + "Your data directory is invalid" : "La cartella dei dati non è valida", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assicurati che ci sia un file \".ocdata\" nella radice della cartella data.", + "Action \"%s\" not supported or implemented." : "Azione \"%s\" non supportata o implementata.", + "Authentication failed, wrong token or provider ID given" : "Autenticazione non riuscita, token o ID fornitore errato ", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametri mancanti per completare la richiesta. Parametri mancanti: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" già utilizzato dal fornitore della federazione cloud \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Il fornitore della federazione cloud con ID: \"%s\" non esiste.", + "Could not obtain lock type %d on \"%s\"." : "Impossibile ottenere il blocco di tipo %d su \"%s\".", + "Storage unauthorized. %s" : "Archiviazione non autorizzata. %s", + "Storage incomplete configuration. %s" : "Configurazione dell'archiviazione incompleta.%s", + "Storage connection error. %s" : "Errore di connessione all'archiviazione. %s", + "Storage is temporarily not available" : "L'archiviazione è temporaneamente non disponibile", + "Storage connection timeout. %s" : "Timeout di connessione all'archiviazione. %s", + "Following databases are supported: %s" : "I seguenti database sono supportati: %s", + "Following platforms are supported: %s" : "Sono supportate le seguenti piattaforme: %s", + "Overview" : "Riepilogo", + "Basic settings" : "Impostazioni di base", + "Sharing" : "Condivisione", + "Security" : "Sicurezza", + "Groupware" : "Groupware", + "Personal info" : "Informazioni personali", + "Mobile & desktop" : "Mobile e desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ciò può essere normalmente corretto fornendo al server web accesso in scrittura alla cartella delle applicazioni o disabilitando il negozio di applicazioni nel file di configurazione. Vedi %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ja.js b/docker/overlays/nextcloud/html/lib/l10n/ja.js new file mode 100644 index 0000000..2f4683f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ja.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\"ディレクトリに書き込めません!", + "This can usually be fixed by giving the webserver write access to the config directory" : "多くの場合、これはWebサーバーにconfigディレクトリへの書き込み権限を与えることで解決できます。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "またはconfig.phpファイルを読み取り専用にしたい場合は、オプション \"config_is_read_only\"をtrueに設定してください。", + "See %s" : "%s を閲覧", + "This can usually be fixed by giving the webserver write access to the config directory." : "多くの場合、これはWebサーバーにconfigディレクトリへの書き込み権限を与えることで解決できます。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "またはconfig.phpファイルを読み取り専用にしたい場合は、オプション \"config_is_read_only\"をtrueに設定してください。 %sを参照してください", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "アプリ %1$s のファイルが正しく置き換えられませんでした。サーバーと互換性のあるバージョンであることを確認してください。", + "Sample configuration detected" : "サンプル設定が見つかりました。", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "サンプル設定がコピーされてそのままです。このままではインストールが失敗し、サポート対象外になります。config.phpを変更する前にドキュメントを確認してください。", + "Other activities" : "その他のアクティビティ", + "%1$s and %2$s" : "%1$s と %2$s", + "%1$s, %2$s and %3$s" : "%1$s と %2$s、%3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s と %2$s、%3$s、%4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s と %2$s、%3$s、%4$s、%5$s", + "Education Edition" : "教育向けエディション", + "Enterprise bundle" : "エンタープライズ バンドル", + "Groupware bundle" : "グループウェア バンドル", + "Hub bundle" : "Hubバンドル", + "Social sharing bundle" : "SNS バンドル", + "PHP %s or higher is required." : "PHP %s 以上が必要です。", + "PHP with a version lower than %s is required." : "%s 以前のバージョンのPHPが必要です。", + "%sbit or higher PHP required." : "%sbit 以上の新しいバージョンのPHPが必要です。", + "The following architectures are supported: %s" : "次のアーキテクチャをサポートしています: %s", + "The following databases are supported: %s" : "次のデータベースをサポートしています: %s", + "The command line tool %s could not be found" : "コマンド '%s' は見つかりませんでした。", + "The library %s is not available." : " %s ライブラリーが利用できません。", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$sライブラリーは、%2$sよりも新しいバージョンが必要です。利用可能なバージョンは、 %3$sです。", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s ライブラリーは、%2$s よりも古いバージョンが必要です。利用可能なバージョンは、%3$s です。", + "The following platforms are supported: %s" : "次のプラットフォームをサポートしています: %s", + "Server version %s or higher is required." : "サーバーの %s よりも高いバージョンが必要です。", + "Server version %s or lower is required." : "サーバーの %s よりも低いバージョンが必要です。", + "Logged in user must be an admin or sub admin" : "ログインユーザーは管理者またはサブ管理者である必要があります", + "Logged in user must be an admin" : "ログインユーザーは管理者である必要があります", + "Wiping of device %s has started" : "端末%sのワイプを開始しました。", + "Wiping of device »%s« has started" : "端末»%s«のワイプを開始しました。", + "»%s« started remote wipe" : "»%s«がリモートワイプを開始しました", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "端末またはアプリケーション»%s«がリモートワイプ処理を開始しました。 プロセスの完了時、電子メールを受信します", + "Wiping of device %s has finished" : "端末%sのワイプが完了しました", + "Wiping of device »%s« has finished" : "端末»%s«のワイプが完了しました", + "»%s« finished remote wipe" : "»%s«のリモートワイプが完了しました", + "Device or application »%s« has finished the remote wipe process." : "端末またはアプリケーション»%s«のリモートワイプ処理が完了しました。 ", + "Remote wipe started" : "リモートワイプを開始しました", + "A remote wipe was started on device %s" : "端末%sでリモートワイプが開始されました", + "Remote wipe finished" : "リモートワイプが完了しました", + "The remote wipe on %s has finished" : "%sのリモートワイプが完了しました", + "Authentication" : "認証", + "Unknown filetype" : "不明なファイルタイプ", + "Invalid image" : "無効な画像", + "Avatar image is not square" : "アバター画像が正方形ではありません", + "today" : "今日", + "tomorrow" : "明日", + "yesterday" : "昨日", + "_in %n day_::_in %n days_" : ["%n 日"], + "_%n day ago_::_%n days ago_" : ["%n 日前"], + "next month" : "来月", + "last month" : "先月", + "_in %n month_::_in %n months_" : ["%n ヶ月"], + "_%n month ago_::_%n months ago_" : ["%n ヶ月前"], + "next year" : "来年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n 年"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n 時間"], + "_%n hour ago_::_%n hours ago_" : ["%n 時間前"], + "_in %n minute_::_in %n minutes_" : ["%n 分"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分前"], + "in a few seconds" : "数秒", + "seconds ago" : "数秒前", + "Empty file" : "空白のファイル", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %sのモジュールは存在しません。アプリ設定で有効にするか、管理者に問い合わせてください。", + "File name is a reserved word" : "ファイル名が予約された単語です", + "File name contains at least one invalid character" : "ファイル名に1文字以上の無効な文字が含まれています", + "File name is too long" : "ファイル名が長すぎます", + "Dot files are not allowed" : "ドットファイルは許可されていません", + "Empty filename is not allowed" : "空のファイル名は許可されていません", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfoファイルが読み込めないため、アプリ名 \"%s\" がインストールできません。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" アプリは、このバージョンのサーバーと互換性がないためインストールされませんでした。", + "__language_name__" : "Japanese (日本語)", + "This is an automatically sent email, please do not reply." : "これは自動的に生成されたメールです。返信しないでください。", + "Help" : "ヘルプ", + "Apps" : "アプリ", + "Settings" : "設定", + "Log out" : "ログアウト", + "Users" : "ユーザー", + "Unknown user" : "不明なユーザー", + "Additional settings" : "追加設定", + "%s enter the database username and name." : "%s データベース名とデータベースのユーザー名を入力してください。", + "%s enter the database username." : "%s のデータベースのユーザー名を入力してください。", + "%s enter the database name." : "%s のデータベース名を入力してください。", + "%s you may not use dots in the database name" : "%s ではデータベース名にドットを利用できないかもしれません。", + "MySQL username and/or password not valid" : "MySQLのユーザー名またはパスワードが有効ではありません", + "You need to enter details of an existing account." : "既存のアカウントの詳細を入力してください。", + "Oracle connection could not be established" : "Oracleへの接続が確立できませんでした。", + "Oracle username and/or password not valid" : "Oracleのユーザー名またはパスワードが有効ではありません", + "PostgreSQL username and/or password not valid" : "PostgreSQLのユーザー名またはパスワードが有効ではありません", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X では、サポートされていません。このOSでは、%sは正常に動作しないかもしれません。ご自身の責任においてご利用ください。", + "For the best results, please consider using a GNU/Linux server instead." : "最も良い方法としては、代わりにGNU/Linuxサーバーを利用することをご検討ください。", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "このインスタンス %s は、32bit PHP 環境で動作しており、php.ini に open_basedir が設定されているようです。4GB以上のファイルで問題が発生するため、この設定を利用しないことをお勧めします。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini から open_basedir 設定を削除するか、64bit PHPに切り替えてください。", + "Set an admin username." : "管理者のユーザー名を設定", + "Set an admin password." : "管理者のパスワードを設定", + "Can't create or write into the data directory %s" : "%s データディレクトリに作成、書き込みができません", + "Invalid Federated Cloud ID" : "無効なクラウド共有ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "%s のバックエンドの共有には、OCP\\Share_Backend インターフェースを実装しなければなりません。", + "Sharing backend %s not found" : "共有バックエンド %s が見つかりません", + "Sharing backend for %s not found" : "%s のための共有バックエンドが見つかりません", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s さんが »%2$s« にノートを追加しました。", + "%1$s shared »%2$s« with you and wants to add" : "%1$s shared »%2$s« with you and wants to add", + "»%s« added a note to a file shared with you" : "»%s« があなたと共有しているファイルにノートを追加しました。 ", + "Open »%s«" : "»%s«を開く", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "%s を共有することを許可されていません。", + "Can’t increase permissions of %s" : "%s の権限を追加できません", + "Files can’t be shared with delete permissions" : "削除権限つきでファイルを共有できません。", + "Files can’t be shared with create permissions" : "作成権限つきでファイルを共有できません。", + "Expiration date is in the past" : "有効期限が切れています", + "Can’t set expiration date more than %s days in the future" : "有効期限を%s日以降に設定できません。", + "%1$s shared »%2$s« with you" : "%1$s は »%2$s« をあなたと共有しました", + "%1$s shared »%2$s« with you." : "%1$sが あなたと »%2$s« を共有しました。", + "Click the button below to open it." : "開くには下のボタンをクリック", + "The requested share does not exist anymore" : "この共有はもう存在しません。", + "Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした", + "Sunday" : "日曜日", + "Monday" : "月曜日", + "Tuesday" : "火曜日", + "Wednesday" : "水曜日", + "Thursday" : "木曜日", + "Friday" : "金曜日", + "Saturday" : "土曜日", + "Sun." : "日", + "Mon." : "月", + "Tue." : "火", + "Wed." : "水", + "Thu." : "木", + "Fri." : "金", + "Sat." : "土", + "Su" : "日", + "Mo" : "月", + "Tu" : "火", + "We" : "水", + "Th" : "木", + "Fr" : "金", + "Sa" : "土", + "January" : "1月", + "February" : "2月", + "March" : "3月", + "April" : "4月", + "May" : "5月", + "June" : "6月", + "July" : "7月", + "August" : "8月", + "September" : "9月", + "October" : "10月", + "November" : "11月", + "December" : "12月", + "Jan." : "1月", + "Feb." : "2月", + "Mar." : "3月", + "Apr." : "4月", + "May." : "5月", + "Jun." : "6月", + "Jul." : "7月", + "Aug." : "8月", + "Sep." : "9月", + "Oct." : "10月", + "Nov." : "11月", + "Dec." : "12月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列は、次のものです: \"a-z\", \"A-Z\", \"0-9\", \"_.@-\"", + "A valid username must be provided" : "有効なユーザー名を指定する必要があります", + "Username contains whitespace at the beginning or at the end" : "ユーザー名の最初か最後に空白が含まれています", + "Username must not consist of dots only" : "ユーザー名は、ドットのみではつけられません", + "Username is invalid because files already exist for this user" : "このユーザーのファイルが既に存在するため、ユーザー名は無効です", + "A valid password must be provided" : "有効なパスワードを指定する必要があります", + "The username is already being used" : "ユーザー名はすでに使われています", + "Could not create user" : "ユーザーを作成できませんでした", + "User disabled" : "ユーザーは無効です", + "Login canceled by app" : "アプリによりログインが中止されました", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "次の依存関係を満たしていないため、アプリ \"%1$s\" をインストールできません: %2$s", + "a safe home for all your data" : "あなたのすべてのデータを安全に保管する場所", + "File is currently busy, please try again later" : "現在ファイルはビジーです。後でもう一度試してください。", + "Can't read file" : "ファイルを読み込めません", + "Application is not enabled" : "アプリケーションは無効です", + "Authentication error" : "認証エラー", + "Token expired. Please reload page." : "トークンが無効になりました。ページを再読込してください。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "データベースドライバー (sqlite, mysql, postgresql) がインストールされていません。", + "Cannot write into \"config\" directory" : "\"config\" ディレクトリに書き込みができません", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "多くの場合、Webサーバーの configディレクトリ に書き込み権限を与えることで直ります。%s を見てください", + "Cannot write into \"apps\" directory" : "\"apps\" ディレクトリに書き込みができません", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "多くの場合、これはWebサーバーにappsディレクトリへの書き込み権限を与えるか、設定ファイルでアプリストアを無効化することで解決できます。", + "Cannot create \"data\" directory" : "\"data\" ディレクトリを作成できません", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "多くの場合、Webサーバーのルートディレクトリに書き込み権限を与えることで直ります。%s を見てください。", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Webサーバーのルートディレクトリに書き込み権限パーミッションが必要です。%s を見てください。", + "Setting locale to %s failed" : "ロケールを %s に設定できませんでした", + "Please install one of these locales on your system and restart your webserver." : "これらのロケールのうちいずれかをシステムにインストールし、Webサーバーを再起動してください。", + "PHP module %s not installed." : "PHP のモジュール %s がインストールされていません。", + "Please ask your server administrator to install the module." : "サーバー管理者にモジュールのインストールを依頼してください。", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP設定の\"%s\"は \"%s\"に設定されていません", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini のこの設定を調整して、再度 Nextcloudを起動してください。", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload の値は \"0\" であるべきですが、\"%s\" に設定されています", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "この問題を修正するには、php.ini ファイルの mbstring.func_overload0 に設定してください。", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 バージョン 2.7.0 が最低必要です。現在 %s がインストールされています。", + "To fix this issue update your libxml2 version and restart your web server." : "この問題を解決するには、libxml2 を更新して、Webサーバーを再起動してください。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHPでインラインドキュメントブロックを取り除く設定になっています。これによりコアアプリで利用できないものがいくつかあります。", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "これは、Zend OPcacheやeAccelerator 等のキャッシュ/アクセラレーターが原因かもしれません。", + "PHP modules have been installed, but they are still listed as missing?" : "PHP モジュールはインストールされていますが、まだ一覧に表示されていますか?", + "Please ask your server administrator to restart the web server." : "サーバー管理者にWebサーバーを再起動するよう依頼してください。", + "PostgreSQL >= 9 required" : "PostgreSQL 9以上が必要です", + "Please upgrade your database version" : "新しいバージョンのデータベースにアップグレードしてください", + "Your data directory is readable by other users" : "データディレクトリは、他のユーザーから読み取り専用です", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "ディレクトリが他のユーザーから見えないように、パーミッションを 0770 に変更してください。", + "Your data directory must be an absolute path" : "データディレクトリは、絶対パスにする必要があります", + "Check the value of \"datadirectory\" in your configuration" : "設定ファイル内の \"datadirectory\" の値を確認してください。", + "Your data directory is invalid" : "データディレクトリが無効です", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "データディレクトリの直下に \".ocdata\" ファイルがあるのを確認してください。", + "Action \"%s\" not supported or implemented." : "アクション「%s」はサポートされていないか、実装されていません。", + "Authentication failed, wrong token or provider ID given" : "認証できませんでした。トークンまたはプロバイダIDが間違っています", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "要求を完了するためのパラメータがありません。欠落したパラメータ:「%s」", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID「%1$s」はクラウドフェデレーションプロバイダ「%2$s」によって既に使用されています", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ID: \"%s\"のクラウドフェデレーションプロバイダは存在しません。", + "Could not obtain lock type %d on \"%s\"." : "\"%s\" で %d タイプのロックを取得できませんでした。", + "Storage unauthorized. %s" : "権限のないストレージです。 %s", + "Storage incomplete configuration. %s" : "設定が未完了のストレージです。 %s", + "Storage connection error. %s" : "ストレージへの接続エラー。 %s", + "Storage is temporarily not available" : "ストレージは一時的に利用できません", + "Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s", + "Following databases are supported: %s" : "次のデータベースをサポートしています: %s", + "Following platforms are supported: %s" : "次のプラットフォームをサポートしています: %s", + "Overview" : "概要", + "Basic settings" : "基本設定", + "Sharing" : "共有", + "Security" : "セキュリティ", + "Groupware" : "グループウェア", + "Personal info" : "個人情報", + "Mobile & desktop" : "モバイル & デスクトップ", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "多くの場合、これは Webサーバーにappsディレクトリへの書き込み権限を与えるか、設定ファイルでアプリストアを無効化することで直ります。%s を見てください。" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ja.json b/docker/overlays/nextcloud/html/lib/l10n/ja.json new file mode 100644 index 0000000..8d40dda --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ja.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\"ディレクトリに書き込めません!", + "This can usually be fixed by giving the webserver write access to the config directory" : "多くの場合、これはWebサーバーにconfigディレクトリへの書き込み権限を与えることで解決できます。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "またはconfig.phpファイルを読み取り専用にしたい場合は、オプション \"config_is_read_only\"をtrueに設定してください。", + "See %s" : "%s を閲覧", + "This can usually be fixed by giving the webserver write access to the config directory." : "多くの場合、これはWebサーバーにconfigディレクトリへの書き込み権限を与えることで解決できます。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "またはconfig.phpファイルを読み取り専用にしたい場合は、オプション \"config_is_read_only\"をtrueに設定してください。 %sを参照してください", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "アプリ %1$s のファイルが正しく置き換えられませんでした。サーバーと互換性のあるバージョンであることを確認してください。", + "Sample configuration detected" : "サンプル設定が見つかりました。", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "サンプル設定がコピーされてそのままです。このままではインストールが失敗し、サポート対象外になります。config.phpを変更する前にドキュメントを確認してください。", + "Other activities" : "その他のアクティビティ", + "%1$s and %2$s" : "%1$s と %2$s", + "%1$s, %2$s and %3$s" : "%1$s と %2$s、%3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s と %2$s、%3$s、%4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s と %2$s、%3$s、%4$s、%5$s", + "Education Edition" : "教育向けエディション", + "Enterprise bundle" : "エンタープライズ バンドル", + "Groupware bundle" : "グループウェア バンドル", + "Hub bundle" : "Hubバンドル", + "Social sharing bundle" : "SNS バンドル", + "PHP %s or higher is required." : "PHP %s 以上が必要です。", + "PHP with a version lower than %s is required." : "%s 以前のバージョンのPHPが必要です。", + "%sbit or higher PHP required." : "%sbit 以上の新しいバージョンのPHPが必要です。", + "The following architectures are supported: %s" : "次のアーキテクチャをサポートしています: %s", + "The following databases are supported: %s" : "次のデータベースをサポートしています: %s", + "The command line tool %s could not be found" : "コマンド '%s' は見つかりませんでした。", + "The library %s is not available." : " %s ライブラリーが利用できません。", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$sライブラリーは、%2$sよりも新しいバージョンが必要です。利用可能なバージョンは、 %3$sです。", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s ライブラリーは、%2$s よりも古いバージョンが必要です。利用可能なバージョンは、%3$s です。", + "The following platforms are supported: %s" : "次のプラットフォームをサポートしています: %s", + "Server version %s or higher is required." : "サーバーの %s よりも高いバージョンが必要です。", + "Server version %s or lower is required." : "サーバーの %s よりも低いバージョンが必要です。", + "Logged in user must be an admin or sub admin" : "ログインユーザーは管理者またはサブ管理者である必要があります", + "Logged in user must be an admin" : "ログインユーザーは管理者である必要があります", + "Wiping of device %s has started" : "端末%sのワイプを開始しました。", + "Wiping of device »%s« has started" : "端末»%s«のワイプを開始しました。", + "»%s« started remote wipe" : "»%s«がリモートワイプを開始しました", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "端末またはアプリケーション»%s«がリモートワイプ処理を開始しました。 プロセスの完了時、電子メールを受信します", + "Wiping of device %s has finished" : "端末%sのワイプが完了しました", + "Wiping of device »%s« has finished" : "端末»%s«のワイプが完了しました", + "»%s« finished remote wipe" : "»%s«のリモートワイプが完了しました", + "Device or application »%s« has finished the remote wipe process." : "端末またはアプリケーション»%s«のリモートワイプ処理が完了しました。 ", + "Remote wipe started" : "リモートワイプを開始しました", + "A remote wipe was started on device %s" : "端末%sでリモートワイプが開始されました", + "Remote wipe finished" : "リモートワイプが完了しました", + "The remote wipe on %s has finished" : "%sのリモートワイプが完了しました", + "Authentication" : "認証", + "Unknown filetype" : "不明なファイルタイプ", + "Invalid image" : "無効な画像", + "Avatar image is not square" : "アバター画像が正方形ではありません", + "today" : "今日", + "tomorrow" : "明日", + "yesterday" : "昨日", + "_in %n day_::_in %n days_" : ["%n 日"], + "_%n day ago_::_%n days ago_" : ["%n 日前"], + "next month" : "来月", + "last month" : "先月", + "_in %n month_::_in %n months_" : ["%n ヶ月"], + "_%n month ago_::_%n months ago_" : ["%n ヶ月前"], + "next year" : "来年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n 年"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n 時間"], + "_%n hour ago_::_%n hours ago_" : ["%n 時間前"], + "_in %n minute_::_in %n minutes_" : ["%n 分"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分前"], + "in a few seconds" : "数秒", + "seconds ago" : "数秒前", + "Empty file" : "空白のファイル", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %sのモジュールは存在しません。アプリ設定で有効にするか、管理者に問い合わせてください。", + "File name is a reserved word" : "ファイル名が予約された単語です", + "File name contains at least one invalid character" : "ファイル名に1文字以上の無効な文字が含まれています", + "File name is too long" : "ファイル名が長すぎます", + "Dot files are not allowed" : "ドットファイルは許可されていません", + "Empty filename is not allowed" : "空のファイル名は許可されていません", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfoファイルが読み込めないため、アプリ名 \"%s\" がインストールできません。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" アプリは、このバージョンのサーバーと互換性がないためインストールされませんでした。", + "__language_name__" : "Japanese (日本語)", + "This is an automatically sent email, please do not reply." : "これは自動的に生成されたメールです。返信しないでください。", + "Help" : "ヘルプ", + "Apps" : "アプリ", + "Settings" : "設定", + "Log out" : "ログアウト", + "Users" : "ユーザー", + "Unknown user" : "不明なユーザー", + "Additional settings" : "追加設定", + "%s enter the database username and name." : "%s データベース名とデータベースのユーザー名を入力してください。", + "%s enter the database username." : "%s のデータベースのユーザー名を入力してください。", + "%s enter the database name." : "%s のデータベース名を入力してください。", + "%s you may not use dots in the database name" : "%s ではデータベース名にドットを利用できないかもしれません。", + "MySQL username and/or password not valid" : "MySQLのユーザー名またはパスワードが有効ではありません", + "You need to enter details of an existing account." : "既存のアカウントの詳細を入力してください。", + "Oracle connection could not be established" : "Oracleへの接続が確立できませんでした。", + "Oracle username and/or password not valid" : "Oracleのユーザー名またはパスワードが有効ではありません", + "PostgreSQL username and/or password not valid" : "PostgreSQLのユーザー名またはパスワードが有効ではありません", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X では、サポートされていません。このOSでは、%sは正常に動作しないかもしれません。ご自身の責任においてご利用ください。", + "For the best results, please consider using a GNU/Linux server instead." : "最も良い方法としては、代わりにGNU/Linuxサーバーを利用することをご検討ください。", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "このインスタンス %s は、32bit PHP 環境で動作しており、php.ini に open_basedir が設定されているようです。4GB以上のファイルで問題が発生するため、この設定を利用しないことをお勧めします。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini から open_basedir 設定を削除するか、64bit PHPに切り替えてください。", + "Set an admin username." : "管理者のユーザー名を設定", + "Set an admin password." : "管理者のパスワードを設定", + "Can't create or write into the data directory %s" : "%s データディレクトリに作成、書き込みができません", + "Invalid Federated Cloud ID" : "無効なクラウド共有ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "%s のバックエンドの共有には、OCP\\Share_Backend インターフェースを実装しなければなりません。", + "Sharing backend %s not found" : "共有バックエンド %s が見つかりません", + "Sharing backend for %s not found" : "%s のための共有バックエンドが見つかりません", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s さんが »%2$s« にノートを追加しました。", + "%1$s shared »%2$s« with you and wants to add" : "%1$s shared »%2$s« with you and wants to add", + "»%s« added a note to a file shared with you" : "»%s« があなたと共有しているファイルにノートを追加しました。 ", + "Open »%s«" : "»%s«を開く", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "%s を共有することを許可されていません。", + "Can’t increase permissions of %s" : "%s の権限を追加できません", + "Files can’t be shared with delete permissions" : "削除権限つきでファイルを共有できません。", + "Files can’t be shared with create permissions" : "作成権限つきでファイルを共有できません。", + "Expiration date is in the past" : "有効期限が切れています", + "Can’t set expiration date more than %s days in the future" : "有効期限を%s日以降に設定できません。", + "%1$s shared »%2$s« with you" : "%1$s は »%2$s« をあなたと共有しました", + "%1$s shared »%2$s« with you." : "%1$sが あなたと »%2$s« を共有しました。", + "Click the button below to open it." : "開くには下のボタンをクリック", + "The requested share does not exist anymore" : "この共有はもう存在しません。", + "Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした", + "Sunday" : "日曜日", + "Monday" : "月曜日", + "Tuesday" : "火曜日", + "Wednesday" : "水曜日", + "Thursday" : "木曜日", + "Friday" : "金曜日", + "Saturday" : "土曜日", + "Sun." : "日", + "Mon." : "月", + "Tue." : "火", + "Wed." : "水", + "Thu." : "木", + "Fri." : "金", + "Sat." : "土", + "Su" : "日", + "Mo" : "月", + "Tu" : "火", + "We" : "水", + "Th" : "木", + "Fr" : "金", + "Sa" : "土", + "January" : "1月", + "February" : "2月", + "March" : "3月", + "April" : "4月", + "May" : "5月", + "June" : "6月", + "July" : "7月", + "August" : "8月", + "September" : "9月", + "October" : "10月", + "November" : "11月", + "December" : "12月", + "Jan." : "1月", + "Feb." : "2月", + "Mar." : "3月", + "Apr." : "4月", + "May." : "5月", + "Jun." : "6月", + "Jul." : "7月", + "Aug." : "8月", + "Sep." : "9月", + "Oct." : "10月", + "Nov." : "11月", + "Dec." : "12月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列は、次のものです: \"a-z\", \"A-Z\", \"0-9\", \"_.@-\"", + "A valid username must be provided" : "有効なユーザー名を指定する必要があります", + "Username contains whitespace at the beginning or at the end" : "ユーザー名の最初か最後に空白が含まれています", + "Username must not consist of dots only" : "ユーザー名は、ドットのみではつけられません", + "Username is invalid because files already exist for this user" : "このユーザーのファイルが既に存在するため、ユーザー名は無効です", + "A valid password must be provided" : "有効なパスワードを指定する必要があります", + "The username is already being used" : "ユーザー名はすでに使われています", + "Could not create user" : "ユーザーを作成できませんでした", + "User disabled" : "ユーザーは無効です", + "Login canceled by app" : "アプリによりログインが中止されました", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "次の依存関係を満たしていないため、アプリ \"%1$s\" をインストールできません: %2$s", + "a safe home for all your data" : "あなたのすべてのデータを安全に保管する場所", + "File is currently busy, please try again later" : "現在ファイルはビジーです。後でもう一度試してください。", + "Can't read file" : "ファイルを読み込めません", + "Application is not enabled" : "アプリケーションは無効です", + "Authentication error" : "認証エラー", + "Token expired. Please reload page." : "トークンが無効になりました。ページを再読込してください。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "データベースドライバー (sqlite, mysql, postgresql) がインストールされていません。", + "Cannot write into \"config\" directory" : "\"config\" ディレクトリに書き込みができません", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "多くの場合、Webサーバーの configディレクトリ に書き込み権限を与えることで直ります。%s を見てください", + "Cannot write into \"apps\" directory" : "\"apps\" ディレクトリに書き込みができません", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "多くの場合、これはWebサーバーにappsディレクトリへの書き込み権限を与えるか、設定ファイルでアプリストアを無効化することで解決できます。", + "Cannot create \"data\" directory" : "\"data\" ディレクトリを作成できません", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "多くの場合、Webサーバーのルートディレクトリに書き込み権限を与えることで直ります。%s を見てください。", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Webサーバーのルートディレクトリに書き込み権限パーミッションが必要です。%s を見てください。", + "Setting locale to %s failed" : "ロケールを %s に設定できませんでした", + "Please install one of these locales on your system and restart your webserver." : "これらのロケールのうちいずれかをシステムにインストールし、Webサーバーを再起動してください。", + "PHP module %s not installed." : "PHP のモジュール %s がインストールされていません。", + "Please ask your server administrator to install the module." : "サーバー管理者にモジュールのインストールを依頼してください。", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP設定の\"%s\"は \"%s\"に設定されていません", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini のこの設定を調整して、再度 Nextcloudを起動してください。", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload の値は \"0\" であるべきですが、\"%s\" に設定されています", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "この問題を修正するには、php.ini ファイルの mbstring.func_overload0 に設定してください。", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 バージョン 2.7.0 が最低必要です。現在 %s がインストールされています。", + "To fix this issue update your libxml2 version and restart your web server." : "この問題を解決するには、libxml2 を更新して、Webサーバーを再起動してください。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHPでインラインドキュメントブロックを取り除く設定になっています。これによりコアアプリで利用できないものがいくつかあります。", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "これは、Zend OPcacheやeAccelerator 等のキャッシュ/アクセラレーターが原因かもしれません。", + "PHP modules have been installed, but they are still listed as missing?" : "PHP モジュールはインストールされていますが、まだ一覧に表示されていますか?", + "Please ask your server administrator to restart the web server." : "サーバー管理者にWebサーバーを再起動するよう依頼してください。", + "PostgreSQL >= 9 required" : "PostgreSQL 9以上が必要です", + "Please upgrade your database version" : "新しいバージョンのデータベースにアップグレードしてください", + "Your data directory is readable by other users" : "データディレクトリは、他のユーザーから読み取り専用です", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "ディレクトリが他のユーザーから見えないように、パーミッションを 0770 に変更してください。", + "Your data directory must be an absolute path" : "データディレクトリは、絶対パスにする必要があります", + "Check the value of \"datadirectory\" in your configuration" : "設定ファイル内の \"datadirectory\" の値を確認してください。", + "Your data directory is invalid" : "データディレクトリが無効です", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "データディレクトリの直下に \".ocdata\" ファイルがあるのを確認してください。", + "Action \"%s\" not supported or implemented." : "アクション「%s」はサポートされていないか、実装されていません。", + "Authentication failed, wrong token or provider ID given" : "認証できませんでした。トークンまたはプロバイダIDが間違っています", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "要求を完了するためのパラメータがありません。欠落したパラメータ:「%s」", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID「%1$s」はクラウドフェデレーションプロバイダ「%2$s」によって既に使用されています", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ID: \"%s\"のクラウドフェデレーションプロバイダは存在しません。", + "Could not obtain lock type %d on \"%s\"." : "\"%s\" で %d タイプのロックを取得できませんでした。", + "Storage unauthorized. %s" : "権限のないストレージです。 %s", + "Storage incomplete configuration. %s" : "設定が未完了のストレージです。 %s", + "Storage connection error. %s" : "ストレージへの接続エラー。 %s", + "Storage is temporarily not available" : "ストレージは一時的に利用できません", + "Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s", + "Following databases are supported: %s" : "次のデータベースをサポートしています: %s", + "Following platforms are supported: %s" : "次のプラットフォームをサポートしています: %s", + "Overview" : "概要", + "Basic settings" : "基本設定", + "Sharing" : "共有", + "Security" : "セキュリティ", + "Groupware" : "グループウェア", + "Personal info" : "個人情報", + "Mobile & desktop" : "モバイル & デスクトップ", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "多くの場合、これは Webサーバーにappsディレクトリへの書き込み権限を与えるか、設定ファイルでアプリストアを無効化することで直ります。%s を見てください。" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ka.js b/docker/overlays/nextcloud/html/lib/l10n/ka.js new file mode 100644 index 0000000..72f8a82 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ka.js @@ -0,0 +1,6 @@ +OC.L10N.register( + "lib", + { + "__language_name__" : "ქართული ენა" +}, +"nplurals=2; plural=(n!=1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ka.json b/docker/overlays/nextcloud/html/lib/l10n/ka.json new file mode 100644 index 0000000..a41a6e4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ka.json @@ -0,0 +1,4 @@ +{ "translations": { + "__language_name__" : "ქართული ენა" +},"pluralForm" :"nplurals=2; plural=(n!=1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ka_GE.js b/docker/overlays/nextcloud/html/lib/l10n/ka_GE.js new file mode 100644 index 0000000..4127b99 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ka_GE.js @@ -0,0 +1,197 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\" დირექტორიაში ჩაწერა ვერ ხერხდება!", + "This can usually be fixed by giving the webserver write access to the config directory" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის კონფიგურაციის დირექტორიაზე წერის უფლებების მინიჭებით", + "See %s" : "იხილეთ %s", + "Sample configuration detected" : "სანიმუშო კონფუგარცია აღმოჩენილია", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "დადგენილია რომ მოხდა სანიმუშო კონფიგურაციის დაკოპირება. ამან შეიძლება გააფუჭოს თქვენი ინსტალაცია და არაა მხარდაჭერილი. გთხოვთ config.php-ში ცვლილებების შეტანამდე გაეცნოთ დოკუმენტაციას.", + "%1$s and %2$s" : "%1$s და %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s და %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s და %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s და %5$s", + "Education Edition" : "საგანმანათლებლო გამოცემა", + "Enterprise bundle" : "ენტერპრაის კონსტრუქცია", + "Groupware bundle" : "ჯგუფური კონსტრუქცია", + "Social sharing bundle" : "სოციალური გაზიარების შეკვრა", + "PHP %s or higher is required." : "PHP %s ან უფრო მაღალია საჭირო.", + "PHP with a version lower than %s is required." : "საჭიროა PHP-ს %s-ზე მცირე ვერსია.", + "%sbit or higher PHP required." : "საჭიროა PHP %s ბიტით ან მეტით.", + "The command line tool %s could not be found" : "command-line ხელსაწყო %s ვერ იქნა ნაპოვნი", + "The library %s is not available." : "ბიბლიოთეკა %s ვერ იქნა ნაპოვნი.", + "Server version %s or higher is required." : "საჭიროა სერვერი ვერსიით %s ან მეტი.", + "Server version %s or lower is required." : "საჭიროა სერვერი ვერსიით %s ან ნაკლები.", + "Logged in user must be an admin" : "ავტორიზირებული მომხმარებელი უნდა იყოს ადმინისტრატორი", + "Authentication" : "აუტენტიფიკაცია", + "Unknown filetype" : "ამოუცნობი ფაილის ტიპი", + "Invalid image" : "არასწორი სურათი", + "Avatar image is not square" : "ავატარის სურათი არაა კვადრატი", + "today" : "დღეს", + "tomorrow" : "ხვალ", + "yesterday" : "გუშინ", + "_in %n day_::_in %n days_" : ["%n დღეში","%n დღეში"], + "_%n day ago_::_%n days ago_" : ["%n დღის წინ","%n დღის წინ"], + "next month" : "შემდეგი თვე", + "last month" : "გასულ თვეში", + "_in %n month_::_in %n months_" : ["%n თვეში","%n თვეში"], + "_%n month ago_::_%n months ago_" : ["%n თვის წინ","%n თვის წინ"], + "next year" : "შემდეგი წელი", + "last year" : "გასულ წელს", + "_in %n year_::_in %n years_" : ["%n წელიწადში","%n წელიწადში"], + "_%n year ago_::_%n years ago_" : ["%n წლის წინ","%n წლის წინ"], + "_in %n hour_::_in %n hours_" : ["%n საათში","%n საათში"], + "_%n hour ago_::_%n hours ago_" : ["%n საათის წინ","%n საათის წინ"], + "_in %n minute_::_in %n minutes_" : ["%n წუთში","%n წუთში"], + "_%n minute ago_::_%n minutes ago_" : ["%n წუთის წინ","%n წუთის წინ"], + "in a few seconds" : "რამდენიმე წამში", + "seconds ago" : "წამის წინ", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "მოდული ID-ით: %s არ არსებობს. გთხოვთ აამოქმედოთ ის აპლიკაციების პარამეტრებში ან დაუკავშირდეთ ადმინისტრატორს.", + "File name is a reserved word" : "ფაილის სახელი რეზერვირებული სიტყვაა", + "File name contains at least one invalid character" : "ფაილის სახელი მოიცავს დაუშვებელ სიმბოლოს", + "File name is too long" : "ფაილის სახელი ზედმეტად გრძელია", + "Dot files are not allowed" : "წერტილოვანი ფაილები არაა ნებადართლი", + "Empty filename is not allowed" : "ცარიელი ფაილის სახელი არაა ნებადართული", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "აპლიკაცია \"%s\" ვერ ყენდება, რადგან appinfo ფაილი არ იკითხება.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "აპლიკაცია \"%s\" ვერ ყენდება, რადგან ის ამ სერვერის ვერსიასთან არაა თავსებადი.", + "__language_name__" : "ქართული", + "This is an automatically sent email, please do not reply." : "ეს ელ-წერილი გამოგზავნილია ავტომატურად, გთხოვთ არ უპასუხოთ.", + "Help" : "დახმარება", + "Apps" : "აპლიკაციები", + "Settings" : "პარამეტრები", + "Log out" : "გასვლა", + "Users" : "მომხმარებლები", + "Unknown user" : "ამოუცნობი მომხმარებელი", + "Additional settings" : "დამატებითი პარამეტრები", + "%s enter the database username and name." : "%s შეიყვანეთ მონაცემთა ბაზის მომხმარებლის სახელი და სახელი.", + "%s enter the database username." : "%s შეიყვანეთ ბაზის მომხმარებლის სახელი.", + "%s enter the database name." : "%s შეიყვანეთ ბაზის სახელი.", + "%s you may not use dots in the database name" : "%s არ მიუთითოთ წერტილი ბაზის სახელში", + "You need to enter details of an existing account." : "საჭიროა შეიყვანოთ არსებული ანგარიშის დეტალები.", + "Oracle connection could not be established" : "Oracle კავშირი ვერ დამყარდა", + "Oracle username and/or password not valid" : "Oracle მომხმარებლის სახელი და/ან პაროლი არ არის სწორი", + "PostgreSQL username and/or password not valid" : "PostgreSQL მომხმარებლის სახელი და/ან პაროლი არ არის სწორი", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X არაა მხარდაჭერილი და %s ამ პლატფორმაზე სწორად არ იმუშავებს. გამოიყენეთ საკუთარი რისკით!", + "For the best results, please consider using a GNU/Linux server instead." : "საუკეთესო შედეგებისთვის სანაცვლოდ გამოიყენეთ GNU/Linux სერვერი.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "როგორც ჩანს ეს %s ინსტანცია მოქმედებს 32-ბიტიან PHP გარემოზე და open_basedir php.ini-ში კონფიგურირებულია. ეს 4 გბ-ზე დიდ ფაილებთან გამოიწვევს პრობლემს და რეკომედირებული არაა.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "გთხოვთ ამოშალოთ open_basedir პარამეტრი php.ini-დან ან გადახვიდეთ 64-ბიტიან PHP-ზე.", + "Set an admin username." : "დააყენეთ ადმინისტრატორის სახელი.", + "Set an admin password." : "დააყენეთ ადმინისტრატორის პაროლი.", + "Can't create or write into the data directory %s" : "მონაცემების დირექტრიის შექმნა ან მასში ჩაწერა ვერ მოხერხდა %s", + "Invalid Federated Cloud ID" : "არასწორი ფედერალური ქლაუდ ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "გაზიარების ბექენდმა %s-მ მოქმედებაში უნდა მოიყვანოს ინტეფეისი OCP\\Share_Backend", + "Sharing backend %s not found" : "გაზიარების ბექენდი %s ვერ იქნა ნაპოვნი", + "Sharing backend for %s not found" : "გაზიარების ბექენდი %s-თვის ვერ იქნა ნაპოვნი", + "Open »%s«" : "გახნსნა »%s«", + "You are not allowed to share %s" : "თქვენ არ გაქვთ უფლება გააზიაროთ %s", + "Can’t increase permissions of %s" : "%s-ის უფლებების გაზრდა ვერ მოხერხდა", + "Files can’t be shared with delete permissions" : "ფაილები გაუქმების უფლებით ვერ გაზიარდება", + "Files can’t be shared with create permissions" : "ფაილები შექმნის უფლებებით ვერ გაზიარდება", + "Expiration date is in the past" : "ვადის ამოწურვის თარიღი წარსულშია", + "Can’t set expiration date more than %s days in the future" : "ვადის ამოწურვის თარიღი %s დღეზე მეტი მომავალში ვერ იქნება", + "Click the button below to open it." : "გასახსნელად დააჭირეთ ქვემოთ მყოფ ღილაკს.", + "The requested share does not exist anymore" : "მოთხოვნილი გაზიარება მეტი აღარ არსებობს", + "Could not find category \"%s\"" : "\"%s\" კატეგორიის მოძებნა ვერ მოხერხდა", + "Sunday" : "კვირა", + "Monday" : "ორშაბათი", + "Tuesday" : "სამშაბათი", + "Wednesday" : "ოთხშაბათი", + "Thursday" : "ხუთშაბათი", + "Friday" : "პარასკევი", + "Saturday" : "შაბათი", + "Sun." : "კვი.", + "Mon." : "ორშ.", + "Tue." : "სამ.", + "Wed." : "ოთხ.", + "Thu." : "ხუთ.", + "Fri." : "პარ.", + "Sat." : "შაბ.", + "Su" : "კვ", + "Mo" : "ორ", + "Tu" : "სა", + "We" : "ოთ", + "Th" : "ხუ", + "Fr" : "პა", + "Sa" : "შა", + "January" : "იანვარი", + "February" : "თებერვალი", + "March" : "მარტი", + "April" : "აპრილი", + "May" : "მაისი", + "June" : "ივნისი", + "July" : "ივლისი", + "August" : "აგვისტო", + "September" : "სექტემბერი", + "October" : "ოქტომბერი", + "November" : "ნოემბერი", + "December" : "დეკემბერი", + "Jan." : "იან.", + "Feb." : "თებ.", + "Mar." : "მარ.", + "Apr." : "აპრ.", + "May." : "მაი.", + "Jun." : "ივნ.", + "Jul." : "ივლ.", + "Aug." : "აგვ.", + "Sep." : "სექ.", + "Oct." : "ოქტ.", + "Nov." : "ნოე.", + "Dec." : "დეკ.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "მომხმარებლის სახელში დაშვებულია მხოლოდ შემდეგი ნიშნები: \"a-z\", \"A-Z\", \"0-9\", და \"_.@-'\"", + "A valid username must be provided" : "უნდა მიუთითოთ არსებული მომხმარებლის სახელი", + "Username contains whitespace at the beginning or at the end" : "მომხმარებლის სახელი დასაწყისსში ან დასასრულში მოიცავს სიცარიელეს", + "Username must not consist of dots only" : "მომხმარებლის სახელი ვერ იქნება შემდგარი მხოლოდ წერტილებისგან", + "A valid password must be provided" : "უნდა მიუთითოთ სწორი პაროლი", + "The username is already being used" : "ესეთი მომხმარებლის სახელი უკვე არსებობს", + "Could not create user" : "მომხმარებლის შექმნა ვერ მოხერხდა", + "User disabled" : "მომხმარებელი გათიშულია", + "Login canceled by app" : "აპლიკაციამ ლოგინი უარყო", + "a safe home for all your data" : "უსაფრთხო სახლი მთელი თქვენი მონაცემებისათვის", + "File is currently busy, please try again later" : "ფაილი ამჟამად დაკავებულია, გთხოვთ მოგვიანებით სცადოთ ახლიდან", + "Can't read file" : "ფაილის წაკითხვა ვერ მოხერხდა", + "Application is not enabled" : "აპლიკაცია არ არის აქტიური", + "Authentication error" : "აუტენტიფიკაციის შეცდომა", + "Token expired. Please reload page." : "ტოკენს ვადა გაუვიდა. გთხოვთ განაახლოთ გვერდი.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "მონაცემთა ბაზის დრაივერები (sqlite, mysql, ან postgresql) დაყენებული არაა.", + "Cannot write into \"config\" directory" : "კონფიგურაციის \"config\" დირექტორიაში ჩაწერა ვერ მოხერხდა", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის კონფიგურაციის დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Cannot write into \"apps\" directory" : "აპლიკაციების \"apps\" დირექტორიაში ჩაწერა ვერ მოხერხდა", + "Cannot create \"data\" directory" : "\"data\" დირექორია ვერ შეიქმნა", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის root დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "უფლებები ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის root დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Setting locale to %s failed" : "ლოკალიზაციის %s-ზე დაყენება ვერ მოხერხდა", + "Please install one of these locales on your system and restart your webserver." : "გთხოვთ დააყენოთ ამ ლოკალიზაციებიდან ერთ-ერთი და გადატვირთოთ ვებ-სერვერი.", + "PHP module %s not installed." : "PHP მოდული %s არაა დაყენებული.", + "Please ask your server administrator to install the module." : "გთხოვთ სთხოვოთ თქვენს სერვერის ადმინისტრატორს დააყენოს მოდული.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP პარამეტრი \"%s\" არ არის დაყენებული \"%s\"-ზე.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "ამ პარამეტრის დაყენება php.ini-ში Nextcloud-ს გაუშვებს ახლიდან", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload მოსალოდნელი მნიშვნელობის \"0\"-ის ნაცვლად დაყენებულია \"%s\"-ზე", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "ამ პრობლემის მოსაგვარებლად php.ini-ში mbstring.func_overload დააყენეთ 0-ზე", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "საჭიროა libxml2 ვერსიით 2.7.0 ან მეტი. ახლა დაყენებულია %s.", + "To fix this issue update your libxml2 version and restart your web server." : "ამ პრობლემის მოსაგვარებლად განაახლეთ libxml2 ვერსია და გადატვირთეთ თქვენი ვებ-სერვერი.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "როგორც ჩანს PHP დაყენებულია ისე, რომ შიდა დოკუმენტური ბლოკები წაშალოს. ეს რამდენიმე მთავარ აპლიკაციას გახდის მიუწვდომელს.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "ეს შესაძლოა გამოწვეული იყოს ისეთი კეშით/აქსელერატორით როგორიცაა Zend OPcache ან eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP მოდულები დაყენდა, მაგრამ მაინც ჩამოწერილია როგორც არმყოფები?", + "Please ask your server administrator to restart the web server." : "გთხოვთ სთხოვოთ თქვენს სერვერის ადმინისტრატორს გადატვირთოს ვებ-სერვერი.", + "PostgreSQL >= 9 required" : "საჭიროა PostgreSQL >= 9", + "Please upgrade your database version" : "გთხოვთ განაახლოთ თქვენი მონაცემთა ბაზის ვერსია", + "Your data directory is readable by other users" : "თქვენი მონაცემების დირექტორია სხვა მომხარებლების მიერ კითხვადია", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "გთხოვთ შეცვალოთ უფლებები 0770-ზე, რათა დირექტორია სხვა მომხმარებლების მიერ აღარ იყოს კითხვადი.", + "Your data directory must be an absolute path" : "თქვენი მონაცემების დირექტორია უნდა იყოს აბოლუტური მისამართი", + "Check the value of \"datadirectory\" in your configuration" : "თქვენს კონფიგურაციაში შეამოწმეთ \"datadiretory\"-ის მნიშვნელობა", + "Your data directory is invalid" : "თქვენი მონაცემების დირექტორია არაა სწორი", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "დარწმუნდით რომ ფაილი სახელად \".ocdata\" მონაცემების დირექტორიის საწყისშია.", + "Could not obtain lock type %d on \"%s\"." : "ჩაკეთვის ტიპის %d მოძიება \"%s\"-ზე ვერ მოხერხდა.", + "Storage unauthorized. %s" : "საცავი არაავტორიზირებულია. %s", + "Storage incomplete configuration. %s" : "საცავის არასრული კონფიგურაცია. %s", + "Storage connection error. %s" : "საცავის კავშირის შეცდომა. %s", + "Storage is temporarily not available" : "საცავი დროებით ხელმიუწვდომელია", + "Storage connection timeout. %s" : "საცავის კავშირის დროის ამოწურვა. %s", + "Following databases are supported: %s" : "მხარდაჭერილია შემდეგი მონაცემთა ბაზები: %s", + "Following platforms are supported: %s" : "მხარდაჭერია შემდეგი პლატფორმები: %s", + "Basic settings" : "ძირითადი პარამეტრები", + "Sharing" : "გაზიარება", + "Security" : "უსაფრთხოება", + "Personal info" : "პირადი ინფორმაცია", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის აპლიკაციების დირექტორიაზე წერის უფლების მინიჭებით ან appstore-ის კონფიგურაციის ფაილით გათიშვით. იხილეთ %s" +}, +"nplurals=2; plural=(n!=1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ka_GE.json b/docker/overlays/nextcloud/html/lib/l10n/ka_GE.json new file mode 100644 index 0000000..3fce936 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ka_GE.json @@ -0,0 +1,195 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\" დირექტორიაში ჩაწერა ვერ ხერხდება!", + "This can usually be fixed by giving the webserver write access to the config directory" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის კონფიგურაციის დირექტორიაზე წერის უფლებების მინიჭებით", + "See %s" : "იხილეთ %s", + "Sample configuration detected" : "სანიმუშო კონფუგარცია აღმოჩენილია", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "დადგენილია რომ მოხდა სანიმუშო კონფიგურაციის დაკოპირება. ამან შეიძლება გააფუჭოს თქვენი ინსტალაცია და არაა მხარდაჭერილი. გთხოვთ config.php-ში ცვლილებების შეტანამდე გაეცნოთ დოკუმენტაციას.", + "%1$s and %2$s" : "%1$s და %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s და %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s და %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s და %5$s", + "Education Edition" : "საგანმანათლებლო გამოცემა", + "Enterprise bundle" : "ენტერპრაის კონსტრუქცია", + "Groupware bundle" : "ჯგუფური კონსტრუქცია", + "Social sharing bundle" : "სოციალური გაზიარების შეკვრა", + "PHP %s or higher is required." : "PHP %s ან უფრო მაღალია საჭირო.", + "PHP with a version lower than %s is required." : "საჭიროა PHP-ს %s-ზე მცირე ვერსია.", + "%sbit or higher PHP required." : "საჭიროა PHP %s ბიტით ან მეტით.", + "The command line tool %s could not be found" : "command-line ხელსაწყო %s ვერ იქნა ნაპოვნი", + "The library %s is not available." : "ბიბლიოთეკა %s ვერ იქნა ნაპოვნი.", + "Server version %s or higher is required." : "საჭიროა სერვერი ვერსიით %s ან მეტი.", + "Server version %s or lower is required." : "საჭიროა სერვერი ვერსიით %s ან ნაკლები.", + "Logged in user must be an admin" : "ავტორიზირებული მომხმარებელი უნდა იყოს ადმინისტრატორი", + "Authentication" : "აუტენტიფიკაცია", + "Unknown filetype" : "ამოუცნობი ფაილის ტიპი", + "Invalid image" : "არასწორი სურათი", + "Avatar image is not square" : "ავატარის სურათი არაა კვადრატი", + "today" : "დღეს", + "tomorrow" : "ხვალ", + "yesterday" : "გუშინ", + "_in %n day_::_in %n days_" : ["%n დღეში","%n დღეში"], + "_%n day ago_::_%n days ago_" : ["%n დღის წინ","%n დღის წინ"], + "next month" : "შემდეგი თვე", + "last month" : "გასულ თვეში", + "_in %n month_::_in %n months_" : ["%n თვეში","%n თვეში"], + "_%n month ago_::_%n months ago_" : ["%n თვის წინ","%n თვის წინ"], + "next year" : "შემდეგი წელი", + "last year" : "გასულ წელს", + "_in %n year_::_in %n years_" : ["%n წელიწადში","%n წელიწადში"], + "_%n year ago_::_%n years ago_" : ["%n წლის წინ","%n წლის წინ"], + "_in %n hour_::_in %n hours_" : ["%n საათში","%n საათში"], + "_%n hour ago_::_%n hours ago_" : ["%n საათის წინ","%n საათის წინ"], + "_in %n minute_::_in %n minutes_" : ["%n წუთში","%n წუთში"], + "_%n minute ago_::_%n minutes ago_" : ["%n წუთის წინ","%n წუთის წინ"], + "in a few seconds" : "რამდენიმე წამში", + "seconds ago" : "წამის წინ", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "მოდული ID-ით: %s არ არსებობს. გთხოვთ აამოქმედოთ ის აპლიკაციების პარამეტრებში ან დაუკავშირდეთ ადმინისტრატორს.", + "File name is a reserved word" : "ფაილის სახელი რეზერვირებული სიტყვაა", + "File name contains at least one invalid character" : "ფაილის სახელი მოიცავს დაუშვებელ სიმბოლოს", + "File name is too long" : "ფაილის სახელი ზედმეტად გრძელია", + "Dot files are not allowed" : "წერტილოვანი ფაილები არაა ნებადართლი", + "Empty filename is not allowed" : "ცარიელი ფაილის სახელი არაა ნებადართული", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "აპლიკაცია \"%s\" ვერ ყენდება, რადგან appinfo ფაილი არ იკითხება.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "აპლიკაცია \"%s\" ვერ ყენდება, რადგან ის ამ სერვერის ვერსიასთან არაა თავსებადი.", + "__language_name__" : "ქართული", + "This is an automatically sent email, please do not reply." : "ეს ელ-წერილი გამოგზავნილია ავტომატურად, გთხოვთ არ უპასუხოთ.", + "Help" : "დახმარება", + "Apps" : "აპლიკაციები", + "Settings" : "პარამეტრები", + "Log out" : "გასვლა", + "Users" : "მომხმარებლები", + "Unknown user" : "ამოუცნობი მომხმარებელი", + "Additional settings" : "დამატებითი პარამეტრები", + "%s enter the database username and name." : "%s შეიყვანეთ მონაცემთა ბაზის მომხმარებლის სახელი და სახელი.", + "%s enter the database username." : "%s შეიყვანეთ ბაზის მომხმარებლის სახელი.", + "%s enter the database name." : "%s შეიყვანეთ ბაზის სახელი.", + "%s you may not use dots in the database name" : "%s არ მიუთითოთ წერტილი ბაზის სახელში", + "You need to enter details of an existing account." : "საჭიროა შეიყვანოთ არსებული ანგარიშის დეტალები.", + "Oracle connection could not be established" : "Oracle კავშირი ვერ დამყარდა", + "Oracle username and/or password not valid" : "Oracle მომხმარებლის სახელი და/ან პაროლი არ არის სწორი", + "PostgreSQL username and/or password not valid" : "PostgreSQL მომხმარებლის სახელი და/ან პაროლი არ არის სწორი", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X არაა მხარდაჭერილი და %s ამ პლატფორმაზე სწორად არ იმუშავებს. გამოიყენეთ საკუთარი რისკით!", + "For the best results, please consider using a GNU/Linux server instead." : "საუკეთესო შედეგებისთვის სანაცვლოდ გამოიყენეთ GNU/Linux სერვერი.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "როგორც ჩანს ეს %s ინსტანცია მოქმედებს 32-ბიტიან PHP გარემოზე და open_basedir php.ini-ში კონფიგურირებულია. ეს 4 გბ-ზე დიდ ფაილებთან გამოიწვევს პრობლემს და რეკომედირებული არაა.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "გთხოვთ ამოშალოთ open_basedir პარამეტრი php.ini-დან ან გადახვიდეთ 64-ბიტიან PHP-ზე.", + "Set an admin username." : "დააყენეთ ადმინისტრატორის სახელი.", + "Set an admin password." : "დააყენეთ ადმინისტრატორის პაროლი.", + "Can't create or write into the data directory %s" : "მონაცემების დირექტრიის შექმნა ან მასში ჩაწერა ვერ მოხერხდა %s", + "Invalid Federated Cloud ID" : "არასწორი ფედერალური ქლაუდ ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "გაზიარების ბექენდმა %s-მ მოქმედებაში უნდა მოიყვანოს ინტეფეისი OCP\\Share_Backend", + "Sharing backend %s not found" : "გაზიარების ბექენდი %s ვერ იქნა ნაპოვნი", + "Sharing backend for %s not found" : "გაზიარების ბექენდი %s-თვის ვერ იქნა ნაპოვნი", + "Open »%s«" : "გახნსნა »%s«", + "You are not allowed to share %s" : "თქვენ არ გაქვთ უფლება გააზიაროთ %s", + "Can’t increase permissions of %s" : "%s-ის უფლებების გაზრდა ვერ მოხერხდა", + "Files can’t be shared with delete permissions" : "ფაილები გაუქმების უფლებით ვერ გაზიარდება", + "Files can’t be shared with create permissions" : "ფაილები შექმნის უფლებებით ვერ გაზიარდება", + "Expiration date is in the past" : "ვადის ამოწურვის თარიღი წარსულშია", + "Can’t set expiration date more than %s days in the future" : "ვადის ამოწურვის თარიღი %s დღეზე მეტი მომავალში ვერ იქნება", + "Click the button below to open it." : "გასახსნელად დააჭირეთ ქვემოთ მყოფ ღილაკს.", + "The requested share does not exist anymore" : "მოთხოვნილი გაზიარება მეტი აღარ არსებობს", + "Could not find category \"%s\"" : "\"%s\" კატეგორიის მოძებნა ვერ მოხერხდა", + "Sunday" : "კვირა", + "Monday" : "ორშაბათი", + "Tuesday" : "სამშაბათი", + "Wednesday" : "ოთხშაბათი", + "Thursday" : "ხუთშაბათი", + "Friday" : "პარასკევი", + "Saturday" : "შაბათი", + "Sun." : "კვი.", + "Mon." : "ორშ.", + "Tue." : "სამ.", + "Wed." : "ოთხ.", + "Thu." : "ხუთ.", + "Fri." : "პარ.", + "Sat." : "შაბ.", + "Su" : "კვ", + "Mo" : "ორ", + "Tu" : "სა", + "We" : "ოთ", + "Th" : "ხუ", + "Fr" : "პა", + "Sa" : "შა", + "January" : "იანვარი", + "February" : "თებერვალი", + "March" : "მარტი", + "April" : "აპრილი", + "May" : "მაისი", + "June" : "ივნისი", + "July" : "ივლისი", + "August" : "აგვისტო", + "September" : "სექტემბერი", + "October" : "ოქტომბერი", + "November" : "ნოემბერი", + "December" : "დეკემბერი", + "Jan." : "იან.", + "Feb." : "თებ.", + "Mar." : "მარ.", + "Apr." : "აპრ.", + "May." : "მაი.", + "Jun." : "ივნ.", + "Jul." : "ივლ.", + "Aug." : "აგვ.", + "Sep." : "სექ.", + "Oct." : "ოქტ.", + "Nov." : "ნოე.", + "Dec." : "დეკ.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "მომხმარებლის სახელში დაშვებულია მხოლოდ შემდეგი ნიშნები: \"a-z\", \"A-Z\", \"0-9\", და \"_.@-'\"", + "A valid username must be provided" : "უნდა მიუთითოთ არსებული მომხმარებლის სახელი", + "Username contains whitespace at the beginning or at the end" : "მომხმარებლის სახელი დასაწყისსში ან დასასრულში მოიცავს სიცარიელეს", + "Username must not consist of dots only" : "მომხმარებლის სახელი ვერ იქნება შემდგარი მხოლოდ წერტილებისგან", + "A valid password must be provided" : "უნდა მიუთითოთ სწორი პაროლი", + "The username is already being used" : "ესეთი მომხმარებლის სახელი უკვე არსებობს", + "Could not create user" : "მომხმარებლის შექმნა ვერ მოხერხდა", + "User disabled" : "მომხმარებელი გათიშულია", + "Login canceled by app" : "აპლიკაციამ ლოგინი უარყო", + "a safe home for all your data" : "უსაფრთხო სახლი მთელი თქვენი მონაცემებისათვის", + "File is currently busy, please try again later" : "ფაილი ამჟამად დაკავებულია, გთხოვთ მოგვიანებით სცადოთ ახლიდან", + "Can't read file" : "ფაილის წაკითხვა ვერ მოხერხდა", + "Application is not enabled" : "აპლიკაცია არ არის აქტიური", + "Authentication error" : "აუტენტიფიკაციის შეცდომა", + "Token expired. Please reload page." : "ტოკენს ვადა გაუვიდა. გთხოვთ განაახლოთ გვერდი.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "მონაცემთა ბაზის დრაივერები (sqlite, mysql, ან postgresql) დაყენებული არაა.", + "Cannot write into \"config\" directory" : "კონფიგურაციის \"config\" დირექტორიაში ჩაწერა ვერ მოხერხდა", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის კონფიგურაციის დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Cannot write into \"apps\" directory" : "აპლიკაციების \"apps\" დირექტორიაში ჩაწერა ვერ მოხერხდა", + "Cannot create \"data\" directory" : "\"data\" დირექორია ვერ შეიქმნა", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის root დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "უფლებები ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის root დირექტორიაზე წერის უფლებების მინიჭებით. იხილეთ %s", + "Setting locale to %s failed" : "ლოკალიზაციის %s-ზე დაყენება ვერ მოხერხდა", + "Please install one of these locales on your system and restart your webserver." : "გთხოვთ დააყენოთ ამ ლოკალიზაციებიდან ერთ-ერთი და გადატვირთოთ ვებ-სერვერი.", + "PHP module %s not installed." : "PHP მოდული %s არაა დაყენებული.", + "Please ask your server administrator to install the module." : "გთხოვთ სთხოვოთ თქვენს სერვერის ადმინისტრატორს დააყენოს მოდული.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP პარამეტრი \"%s\" არ არის დაყენებული \"%s\"-ზე.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "ამ პარამეტრის დაყენება php.ini-ში Nextcloud-ს გაუშვებს ახლიდან", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload მოსალოდნელი მნიშვნელობის \"0\"-ის ნაცვლად დაყენებულია \"%s\"-ზე", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "ამ პრობლემის მოსაგვარებლად php.ini-ში mbstring.func_overload დააყენეთ 0-ზე", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "საჭიროა libxml2 ვერსიით 2.7.0 ან მეტი. ახლა დაყენებულია %s.", + "To fix this issue update your libxml2 version and restart your web server." : "ამ პრობლემის მოსაგვარებლად განაახლეთ libxml2 ვერსია და გადატვირთეთ თქვენი ვებ-სერვერი.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "როგორც ჩანს PHP დაყენებულია ისე, რომ შიდა დოკუმენტური ბლოკები წაშალოს. ეს რამდენიმე მთავარ აპლიკაციას გახდის მიუწვდომელს.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "ეს შესაძლოა გამოწვეული იყოს ისეთი კეშით/აქსელერატორით როგორიცაა Zend OPcache ან eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP მოდულები დაყენდა, მაგრამ მაინც ჩამოწერილია როგორც არმყოფები?", + "Please ask your server administrator to restart the web server." : "გთხოვთ სთხოვოთ თქვენს სერვერის ადმინისტრატორს გადატვირთოს ვებ-სერვერი.", + "PostgreSQL >= 9 required" : "საჭიროა PostgreSQL >= 9", + "Please upgrade your database version" : "გთხოვთ განაახლოთ თქვენი მონაცემთა ბაზის ვერსია", + "Your data directory is readable by other users" : "თქვენი მონაცემების დირექტორია სხვა მომხარებლების მიერ კითხვადია", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "გთხოვთ შეცვალოთ უფლებები 0770-ზე, რათა დირექტორია სხვა მომხმარებლების მიერ აღარ იყოს კითხვადი.", + "Your data directory must be an absolute path" : "თქვენი მონაცემების დირექტორია უნდა იყოს აბოლუტური მისამართი", + "Check the value of \"datadirectory\" in your configuration" : "თქვენს კონფიგურაციაში შეამოწმეთ \"datadiretory\"-ის მნიშვნელობა", + "Your data directory is invalid" : "თქვენი მონაცემების დირექტორია არაა სწორი", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "დარწმუნდით რომ ფაილი სახელად \".ocdata\" მონაცემების დირექტორიის საწყისშია.", + "Could not obtain lock type %d on \"%s\"." : "ჩაკეთვის ტიპის %d მოძიება \"%s\"-ზე ვერ მოხერხდა.", + "Storage unauthorized. %s" : "საცავი არაავტორიზირებულია. %s", + "Storage incomplete configuration. %s" : "საცავის არასრული კონფიგურაცია. %s", + "Storage connection error. %s" : "საცავის კავშირის შეცდომა. %s", + "Storage is temporarily not available" : "საცავი დროებით ხელმიუწვდომელია", + "Storage connection timeout. %s" : "საცავის კავშირის დროის ამოწურვა. %s", + "Following databases are supported: %s" : "მხარდაჭერილია შემდეგი მონაცემთა ბაზები: %s", + "Following platforms are supported: %s" : "მხარდაჭერია შემდეგი პლატფორმები: %s", + "Basic settings" : "ძირითადი პარამეტრები", + "Sharing" : "გაზიარება", + "Security" : "უსაფრთხოება", + "Personal info" : "პირადი ინფორმაცია", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "ეს ჩვეულებრივ გამოსწორებადია ვებსერვერისთვის აპლიკაციების დირექტორიაზე წერის უფლების მინიჭებით ან appstore-ის კონფიგურაციის ფაილით გათიშვით. იხილეთ %s" +},"pluralForm" :"nplurals=2; plural=(n!=1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/kab.js b/docker/overlays/nextcloud/html/lib/l10n/kab.js new file mode 100644 index 0000000..b266c96 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/kab.js @@ -0,0 +1,6 @@ +OC.L10N.register( + "lib", + { + "Settings" : "Iɣewwaṛen" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/kab.json b/docker/overlays/nextcloud/html/lib/l10n/kab.json new file mode 100644 index 0000000..22b4279 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/kab.json @@ -0,0 +1,4 @@ +{ "translations": { + "Settings" : "Iɣewwaṛen" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/km.js b/docker/overlays/nextcloud/html/lib/l10n/km.js new file mode 100644 index 0000000..5dda6a7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/km.js @@ -0,0 +1,73 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "មិន​ស្គាល់​ប្រភេទ​ឯកសារ", + "Invalid image" : "រូបភាព​មិន​ត្រឹម​ត្រូវ", + "today" : "ថ្ងៃនេះ", + "yesterday" : "ម្សិលមិញ", + "last month" : "ខែមុន", + "_%n month ago_::_%n months ago_" : ["%n ខែ​មុន"], + "last year" : "ឆ្នាំ​មុន", + "_%n hour ago_::_%n hours ago_" : ["%n ម៉ោង​មុន"], + "_%n minute ago_::_%n minutes ago_" : ["%n នាទី​មុន"], + "seconds ago" : "វិនាទី​មុន", + "__language_name__" : "ភាសាខ្មែរ", + "Help" : "ជំនួយ", + "Apps" : "កម្មវិធី", + "Settings" : "ការកំណត់", + "Log out" : "ចាក​ចេញ", + "Users" : "អ្នកប្រើ", + "Unknown user" : "មិនស្គាល់អ្នកប្រើប្រាស់", + "%s enter the database username." : "%s វាយ​បញ្ចូល​ឈ្មោះ​អ្នក​ប្រើ​មូលដ្ឋាន​ទិន្នន័យ។", + "%s enter the database name." : "%s វាយ​បញ្ចូល​ឈ្មោះ​មូលដ្ឋាន​ទិន្នន័យ។", + "%s you may not use dots in the database name" : "%s អ្នក​អាច​មិន​ប្រើ​សញ្ញា​ចុច​នៅ​ក្នុង​ឈ្មោះ​មូលដ្ឋាន​ទិន្នន័យ", + "Oracle connection could not be established" : "មិន​អាច​បង្កើត​ការ​តភ្ជាប់ Oracle", + "PostgreSQL username and/or password not valid" : "ឈ្មោះ​អ្នក​ប្រើ និង/ឬ ពាក្យ​សម្ងាត់ PostgreSQL គឺ​មិន​ត្រូវ​ទេ", + "Set an admin username." : "កំណត់​ឈ្មោះ​អ្នក​គ្រប់គ្រង។", + "Set an admin password." : "កំណត់​ពាក្យ​សម្ងាត់​អ្នក​គ្រប់គ្រង។", + "Could not find category \"%s\"" : "រក​មិន​ឃើញ​ចំណាត់​ក្រុម \"%s\"", + "Sunday" : "ថ្ងៃអាទិត្យ", + "Monday" : "ថ្ងៃចន្ទ", + "Tuesday" : "ថ្ងៃអង្គារ", + "Wednesday" : "ថ្ងៃពុធ", + "Thursday" : "ថ្ងៃព្រហស្បតិ៍", + "Friday" : "ថ្ងៃសុក្រ", + "Saturday" : "ថ្ងៃសៅរ៍", + "Sun." : "អាទិត្យ", + "Mon." : "ចន្ទ", + "Tue." : "អង្គារ", + "Wed." : "ពុធ", + "Thu." : "ព្រហ.", + "Fri." : "សុក្រ", + "Sat." : "សៅរ៍", + "January" : "ខែមករា", + "February" : "ខែកុម្ភៈ", + "March" : "ខែមីនា", + "April" : "ខែមេសា", + "May" : "ខែឧសភា", + "June" : "ខែមិថុនា", + "July" : "ខែកក្កដា", + "August" : "ខែសីហា", + "September" : "ខែកញ្ញា", + "October" : "ខែតុលា", + "November" : "ខែវិច្ឆិកា", + "December" : "ខែធ្នូ", + "Jan." : "មករា", + "Feb." : "កុម្ភៈ", + "Mar." : "មីនា", + "Apr." : "មេសា", + "May." : "ឧសភា", + "Jun." : "មិថុនា", + "Jul." : "កក្កដា", + "Aug." : "សីហា", + "Sep." : "កញ្ញា", + "Oct." : "តុលា", + "Nov." : "វិច្ឆិកា", + "Dec." : "ធ្នូ", + "A valid username must be provided" : "ត្រូវ​ផ្ដល់​ឈ្មោះ​អ្នក​ប្រើ​ឲ្យ​បាន​ត្រឹម​ត្រូវ", + "A valid password must be provided" : "ត្រូវ​ផ្ដល់​ពាក្យ​សម្ងាត់​ឲ្យ​បាន​ត្រឹម​ត្រូវ", + "Application is not enabled" : "មិន​បាន​បើក​កម្មវិធី", + "Authentication error" : "កំហុស​ការ​ផ្ទៀង​ផ្ទាត់​ភាព​ត្រឹម​ត្រូវ", + "Sharing" : "ការ​ចែក​រំលែក" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/km.json b/docker/overlays/nextcloud/html/lib/l10n/km.json new file mode 100644 index 0000000..5859939 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/km.json @@ -0,0 +1,71 @@ +{ "translations": { + "Unknown filetype" : "មិន​ស្គាល់​ប្រភេទ​ឯកសារ", + "Invalid image" : "រូបភាព​មិន​ត្រឹម​ត្រូវ", + "today" : "ថ្ងៃនេះ", + "yesterday" : "ម្សិលមិញ", + "last month" : "ខែមុន", + "_%n month ago_::_%n months ago_" : ["%n ខែ​មុន"], + "last year" : "ឆ្នាំ​មុន", + "_%n hour ago_::_%n hours ago_" : ["%n ម៉ោង​មុន"], + "_%n minute ago_::_%n minutes ago_" : ["%n នាទី​មុន"], + "seconds ago" : "វិនាទី​មុន", + "__language_name__" : "ភាសាខ្មែរ", + "Help" : "ជំនួយ", + "Apps" : "កម្មវិធី", + "Settings" : "ការកំណត់", + "Log out" : "ចាក​ចេញ", + "Users" : "អ្នកប្រើ", + "Unknown user" : "មិនស្គាល់អ្នកប្រើប្រាស់", + "%s enter the database username." : "%s វាយ​បញ្ចូល​ឈ្មោះ​អ្នក​ប្រើ​មូលដ្ឋាន​ទិន្នន័យ។", + "%s enter the database name." : "%s វាយ​បញ្ចូល​ឈ្មោះ​មូលដ្ឋាន​ទិន្នន័យ។", + "%s you may not use dots in the database name" : "%s អ្នក​អាច​មិន​ប្រើ​សញ្ញា​ចុច​នៅ​ក្នុង​ឈ្មោះ​មូលដ្ឋាន​ទិន្នន័យ", + "Oracle connection could not be established" : "មិន​អាច​បង្កើត​ការ​តភ្ជាប់ Oracle", + "PostgreSQL username and/or password not valid" : "ឈ្មោះ​អ្នក​ប្រើ និង/ឬ ពាក្យ​សម្ងាត់ PostgreSQL គឺ​មិន​ត្រូវ​ទេ", + "Set an admin username." : "កំណត់​ឈ្មោះ​អ្នក​គ្រប់គ្រង។", + "Set an admin password." : "កំណត់​ពាក្យ​សម្ងាត់​អ្នក​គ្រប់គ្រង។", + "Could not find category \"%s\"" : "រក​មិន​ឃើញ​ចំណាត់​ក្រុម \"%s\"", + "Sunday" : "ថ្ងៃអាទិត្យ", + "Monday" : "ថ្ងៃចន្ទ", + "Tuesday" : "ថ្ងៃអង្គារ", + "Wednesday" : "ថ្ងៃពុធ", + "Thursday" : "ថ្ងៃព្រហស្បតិ៍", + "Friday" : "ថ្ងៃសុក្រ", + "Saturday" : "ថ្ងៃសៅរ៍", + "Sun." : "អាទិត្យ", + "Mon." : "ចន្ទ", + "Tue." : "អង្គារ", + "Wed." : "ពុធ", + "Thu." : "ព្រហ.", + "Fri." : "សុក្រ", + "Sat." : "សៅរ៍", + "January" : "ខែមករា", + "February" : "ខែកុម្ភៈ", + "March" : "ខែមីនា", + "April" : "ខែមេសា", + "May" : "ខែឧសភា", + "June" : "ខែមិថុនា", + "July" : "ខែកក្កដា", + "August" : "ខែសីហា", + "September" : "ខែកញ្ញា", + "October" : "ខែតុលា", + "November" : "ខែវិច្ឆិកា", + "December" : "ខែធ្នូ", + "Jan." : "មករា", + "Feb." : "កុម្ភៈ", + "Mar." : "មីនា", + "Apr." : "មេសា", + "May." : "ឧសភា", + "Jun." : "មិថុនា", + "Jul." : "កក្កដា", + "Aug." : "សីហា", + "Sep." : "កញ្ញា", + "Oct." : "តុលា", + "Nov." : "វិច្ឆិកា", + "Dec." : "ធ្នូ", + "A valid username must be provided" : "ត្រូវ​ផ្ដល់​ឈ្មោះ​អ្នក​ប្រើ​ឲ្យ​បាន​ត្រឹម​ត្រូវ", + "A valid password must be provided" : "ត្រូវ​ផ្ដល់​ពាក្យ​សម្ងាត់​ឲ្យ​បាន​ត្រឹម​ត្រូវ", + "Application is not enabled" : "មិន​បាន​បើក​កម្មវិធី", + "Authentication error" : "កំហុស​ការ​ផ្ទៀង​ផ្ទាត់​ភាព​ត្រឹម​ត្រូវ", + "Sharing" : "ការ​ចែក​រំលែក" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/kn.js b/docker/overlays/nextcloud/html/lib/l10n/kn.js new file mode 100644 index 0000000..e0e5498 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/kn.js @@ -0,0 +1,36 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "ಅಪರಿಚಿತ ಕಡತ ಮಾದರಿ", + "Invalid image" : "ಅಸಾಮರ್ಥ್ಯ ಚಿತ್ರ", + "__language_name__" : "ಕನ್ನಡ", + "Help" : "ಸಹಾಯ", + "Apps" : "ಕಾರ್ಯಕ್ರಮಗಳು", + "Settings" : "ಆಯ್ಕೆ", + "Log out" : "ಈ ಆವೃತ್ತಿ ಇಂದ ನಿರ್ಗಮಿಸಿ", + "Users" : "ಬಳಕೆದಾರರು", + "Sunday" : "ಭಾನುವಾರ", + "Monday" : "ಸೋಮವಾರ", + "Tuesday" : "ಮಂಗಳವಾರ", + "Wednesday" : "ಬುಧವಾರ", + "Thursday" : "ಗುರುವಾರ", + "Friday" : "ಶುಕ್ರವಾರ", + "Saturday" : "ಶನಿವಾರ", + "January" : "ಜನವರಿ", + "February" : "ಫೆಬ್ರುವರಿ", + "March" : "ಮಾರ್ಚ್", + "April" : "ಏಪ್ರಿಲ್", + "May" : "ಮೇ", + "June" : "ಜೂನ್", + "July" : "ಜುಲೈ", + "August" : "ಆಗಸ್ಟ್", + "September" : "ಸೆಪ್ಟೆಂಬರ್", + "October" : "ಅಕ್ಟೋಬರ್", + "November" : "ನವೆಂಬರ್", + "December" : "ಡಿಸೆಂಬರ್", + "A valid username must be provided" : "ಮಾನ್ಯ ಬಳಕೆದಾರ ಹೆಸರು ಒದಗಿಸಬೇಕಾಗುತ್ತದೆ", + "A valid password must be provided" : "ಸರಿಯಾದ ಬಳಕೆದಾರ ಗುಪ್ತಪದ ಒದಗಿಸಬೇಕಾಗಿದೆ", + "Authentication error" : "ದೃಢೀಕರಣ ದೋಷ", + "Sharing" : "ಹಂಚಿಕೆ" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/kn.json b/docker/overlays/nextcloud/html/lib/l10n/kn.json new file mode 100644 index 0000000..22cb4e1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/kn.json @@ -0,0 +1,34 @@ +{ "translations": { + "Unknown filetype" : "ಅಪರಿಚಿತ ಕಡತ ಮಾದರಿ", + "Invalid image" : "ಅಸಾಮರ್ಥ್ಯ ಚಿತ್ರ", + "__language_name__" : "ಕನ್ನಡ", + "Help" : "ಸಹಾಯ", + "Apps" : "ಕಾರ್ಯಕ್ರಮಗಳು", + "Settings" : "ಆಯ್ಕೆ", + "Log out" : "ಈ ಆವೃತ್ತಿ ಇಂದ ನಿರ್ಗಮಿಸಿ", + "Users" : "ಬಳಕೆದಾರರು", + "Sunday" : "ಭಾನುವಾರ", + "Monday" : "ಸೋಮವಾರ", + "Tuesday" : "ಮಂಗಳವಾರ", + "Wednesday" : "ಬುಧವಾರ", + "Thursday" : "ಗುರುವಾರ", + "Friday" : "ಶುಕ್ರವಾರ", + "Saturday" : "ಶನಿವಾರ", + "January" : "ಜನವರಿ", + "February" : "ಫೆಬ್ರುವರಿ", + "March" : "ಮಾರ್ಚ್", + "April" : "ಏಪ್ರಿಲ್", + "May" : "ಮೇ", + "June" : "ಜೂನ್", + "July" : "ಜುಲೈ", + "August" : "ಆಗಸ್ಟ್", + "September" : "ಸೆಪ್ಟೆಂಬರ್", + "October" : "ಅಕ್ಟೋಬರ್", + "November" : "ನವೆಂಬರ್", + "December" : "ಡಿಸೆಂಬರ್", + "A valid username must be provided" : "ಮಾನ್ಯ ಬಳಕೆದಾರ ಹೆಸರು ಒದಗಿಸಬೇಕಾಗುತ್ತದೆ", + "A valid password must be provided" : "ಸರಿಯಾದ ಬಳಕೆದಾರ ಗುಪ್ತಪದ ಒದಗಿಸಬೇಕಾಗಿದೆ", + "Authentication error" : "ದೃಢೀಕರಣ ದೋಷ", + "Sharing" : "ಹಂಚಿಕೆ" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ko.js b/docker/overlays/nextcloud/html/lib/l10n/ko.js new file mode 100644 index 0000000..f999ce9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ko.js @@ -0,0 +1,212 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\" 디렉터리에 기록할 수 없습니다!", + "This can usually be fixed by giving the webserver write access to the config directory" : "config 디렉터리에 웹 서버 쓰기 권한을 부여해서 해결할 수 있습니다", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오.", + "See %s" : "%s 보기", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오. %s를 참조하십시오.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "앱 %1$s의 파일이 올바르게 교체되지 않았습니다. 서버와 호환되는 버전인지 확인하십시오.", + "Sample configuration detected" : "예제 설정 감지됨", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "예제 설정이 복사된 것 같습니다. 올바르게 작동하지 않을 수도 있기 때문에 지원되지 않습니다. config.php를 변경하기 전 문서를 읽어 보십시오", + "%1$s and %2$s" : "%1$s 및 %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s 및 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s 및 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s 및 %5$s", + "Education Edition" : "교육용 에디션", + "Enterprise bundle" : "엔터프라이즈 번들", + "Groupware bundle" : "그룹웨어 번들", + "Social sharing bundle" : "소셜 공유 번들", + "PHP %s or higher is required." : "PHP 버전 %s 이상이 필요합니다.", + "PHP with a version lower than %s is required." : "PHP 버전 %s 미만이 필요합니다.", + "%sbit or higher PHP required." : "%s비트 이상의 PHP가 필요합니다.", + "The command line tool %s could not be found" : "명령행 도구 %s을(를) 찾을 수 없습니다", + "The library %s is not available." : "%s 라이브러리를 사용할 수 없습니다.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.", + "Server version %s or higher is required." : "서버 버전 %s 이상이 필요합니다.", + "Server version %s or lower is required." : "서버 버전 %s 미만이 필요합니다.", + "Logged in user must be an admin or sub admin" : "로그인한 사용자는 관리자 또는 부 관리자여야 합니다.", + "Logged in user must be an admin" : "로그인한 사용자는 관리자여야 합니다.", + "Wiping of device %s has started" : "디바이스 %s의 완전 삭제가 시작되었습니다.", + "Wiping of device »%s« has started" : "디바이스 »%s«의 완전 삭제가 시작되었습니다.", + "Authentication" : "인증", + "Unknown filetype" : "알 수 없는 파일 형식", + "Invalid image" : "잘못된 사진", + "Avatar image is not square" : "아바타 사진이 정사각형이 아님", + "today" : "오늘", + "tomorrow" : "내일", + "yesterday" : "어제", + "_in %n day_::_in %n days_" : ["%n일 후"], + "_%n day ago_::_%n days ago_" : ["%n일 전"], + "next month" : "다음 달", + "last month" : "지난 달", + "_in %n month_::_in %n months_" : ["%n개월 후"], + "_%n month ago_::_%n months ago_" : ["%n개월 전 "], + "next year" : "내년", + "last year" : "작년", + "_in %n year_::_in %n years_" : ["%n년 후"], + "_%n year ago_::_%n years ago_" : ["%n년 전"], + "_in %n hour_::_in %n hours_" : ["%n시간 후"], + "_%n hour ago_::_%n hours ago_" : ["%n시간 전"], + "_in %n minute_::_in %n minutes_" : ["%n분 후"], + "_%n minute ago_::_%n minutes ago_" : ["%n분 전"], + "in a few seconds" : "몇 초 후", + "seconds ago" : "초 전", + "Empty file" : "빈 파일", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %s인 모듈이 존재하지 않습니다. 앱 설정에서 확인하거나 시스템 관리자에게 연락하십시오.", + "File name is a reserved word" : "파일 이름이 예약된 단어임", + "File name contains at least one invalid character" : "파일 이름에 잘못된 글자가 한 자 이상 있음", + "File name is too long" : "파일 이름이 너무 김", + "Dot files are not allowed" : "점으로 시작하는 파일은 허용되지 않음", + "Empty filename is not allowed" : "파일 이름을 비워 둘 수 없음", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfo 파일을 읽을 수 없어서 앱 \"%s\"을(를) 설치할 수 없습니다.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "이 서버 버전과 호환되지 않아서 앱 \"%s\"을(를) 설치할 수 없습니다", + "__language_name__" : "한국어", + "This is an automatically sent email, please do not reply." : "자동으로 전송한 이메일입니다. 답장하지 마십시오.", + "Help" : "도움말", + "Apps" : "앱", + "Settings" : "설정", + "Log out" : "로그아웃", + "Users" : "사용자", + "Unknown user" : "알려지지 않은 사용자", + "Additional settings" : "고급 설정", + "%s enter the database username and name." : "%s 데이터베이스 사용자 이름과 이름을 입력해 주십시오.", + "%s enter the database username." : "%s 데이터베이스 사용자 이름을 입력해 주십시오.", + "%s enter the database name." : "%s 데이터베이스 이름을 입력하십시오.", + "%s you may not use dots in the database name" : "%s 데이터베이스 이름에는 마침표를 사용할 수 없습니다", + "You need to enter details of an existing account." : "존재하는 계정 정보를 입력해야 합니다.", + "Oracle connection could not be established" : "Oracle 연결을 수립할 수 없습니다.", + "Oracle username and/or password not valid" : "Oracle 사용자 이름이나 암호가 잘못되었습니다.", + "PostgreSQL username and/or password not valid" : "PostgreSQL 사용자 이름 또는 암호가 잘못되었습니다", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X은 지원하지 않으며 %s이(가) 이 플랫폼에서 올바르게 작동하지 않을 수도 있습니다. 본인 책임으로 사용하십시오! ", + "For the best results, please consider using a GNU/Linux server instead." : "더 좋은 결과를 얻으려면 GNU/Linux 서버를 사용하는 것을 권장합니다.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "%s 인스턴스가 32비트 PHP 환경에서 실행 중이고 php.ini에 open_basedir이 설정되어 있습니다. 4GB 이상의 파일 처리에 문제가 생길 수 있으므로 추천하지 않습니다.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini의 open_basedir 설정을 삭제하거나 64비트 PHP로 전환하십시오.", + "Set an admin username." : "관리자의 사용자 이름을 설정합니다.", + "Set an admin password." : "관리자의 암호를 설정합니다.", + "Can't create or write into the data directory %s" : "데이터 디렉터리 %s을(를) 만들거나 기록할 수 없음", + "Invalid Federated Cloud ID" : "잘못된 연합 클라우드 ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "공유 백엔드 %s에서 OCP\\Share_Backend 인터페이스를 구현해야 함", + "Sharing backend %s not found" : "공유 백엔드 %s을(를) 찾을 수 없음", + "Sharing backend for %s not found" : "%s의 공유 백엔드를 찾을 수 없음", + "Open »%s«" : "%s 열기", + "%1$s via %2$s" : "%1$s(%2$s 경유)", + "You are not allowed to share %s" : "%s을(를) 공유할 수 있는 권한이 없습니다", + "Can’t increase permissions of %s" : "%s의 권한을 늘릴 수 없습니다", + "Files can’t be shared with delete permissions" : "파일을 삭제 권한으로 공유할 수 없습니다", + "Files can’t be shared with create permissions" : "파일을 생성 권한으로 공유할 수 없습니다", + "Expiration date is in the past" : "만료 날짜가 과거입니다", + "Can’t set expiration date more than %s days in the future" : "만료 날짜를 %s일 이상 이후로 설정할 수 없습니다", + "Click the button below to open it." : "아래 단추를 눌러서 열 수 있습니다.", + "The requested share does not exist anymore" : "요청한 공유가 더 이상 존재하지 않습니다", + "Could not find category \"%s\"" : "분류 \"%s\"을(를) 찾을 수 없습니다", + "Sunday" : "일요일", + "Monday" : "월요일", + "Tuesday" : "화요일", + "Wednesday" : "수요일", + "Thursday" : "목요일", + "Friday" : "금요일", + "Saturday" : "토요일", + "Sun." : "일", + "Mon." : "월", + "Tue." : "화", + "Wed." : "수", + "Thu." : "목", + "Fri." : "금", + "Sat." : "토", + "Su" : "일", + "Mo" : "월", + "Tu" : "화", + "We" : "수", + "Th" : "목", + "Fr" : "금", + "Sa" : "토", + "January" : "1월", + "February" : "2월", + "March" : "3월", + "April" : "4월", + "May" : "5월", + "June" : "6월", + "July" : "7월", + "August" : "8월", + "September" : "9월", + "October" : "10월", + "November" : "11월", + "December" : "12월", + "Jan." : "1월", + "Feb." : "2월", + "Mar." : "3월", + "Apr." : "4월", + "May." : "5월", + "Jun." : "6월", + "Jul." : "7월", + "Aug." : "8월", + "Sep." : "9월", + "Oct." : "10월", + "Nov." : "11월", + "Dec." : "12월", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "다음 문자만 이름에 사용할 수 있습니다: \"a-z\", \"A-Z\", \"0-9\", 및 \"_.@-'\"", + "A valid username must be provided" : "올바른 사용자 이름을 입력해야 합니다", + "Username contains whitespace at the beginning or at the end" : "사용자 이름의 시작이나 끝에 공백이 있습니다", + "Username must not consist of dots only" : "사용자 이름에 마침표만 있으면 안 됩니다", + "Username is invalid because files already exist for this user" : "무효한 사용자이름. 이미 존재함", + "A valid password must be provided" : "올바른 암호를 입력해야 합니다", + "The username is already being used" : "사용자 이름이 이미 존재합니다", + "Could not create user" : "사용자를 만들 수 없습니다", + "User disabled" : "사용자 비활성화됨", + "Login canceled by app" : "앱에서 로그인 취소함", + "a safe home for all your data" : "내 모든 데이터의 안전한 저장소", + "File is currently busy, please try again later" : "파일이 현재 사용 중, 나중에 다시 시도하십시오", + "Can't read file" : "파일을 읽을 수 없음", + "Application is not enabled" : "앱이 활성화되지 않았습니다", + "Authentication error" : "인증 오류", + "Token expired. Please reload page." : "토큰이 만료되었습니다. 페이지를 새로 고치십시오.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "데이터베이스 드라이버(sqlite, mysql, postgresql)가 설치되지 않았습니다.", + "Cannot write into \"config\" directory" : "\"config\" 디렉터리에 기록할 수 없습니다", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "config 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오", + "Cannot write into \"apps\" directory" : "\"apps\" 디렉터리에 기록할 수 없습니다", + "Cannot create \"data\" directory" : "\"data\" 디렉터리를 만들 수 없음", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "루트 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "루트 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오.", + "Setting locale to %s failed" : "로캘을 %s(으)로 설정할 수 없음", + "Please install one of these locales on your system and restart your webserver." : "다음 중 하나 이상의 로캘을 시스템에 설치하고 웹 서버를 다시 시작하십시오.", + "PHP module %s not installed." : "PHP 모듈 %s이(가) 설치되지 않았습니다.", + "Please ask your server administrator to install the module." : "서버 관리자에게 모듈 설치를 요청하십시오.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 설정 \"%s\"이(가) \"%s\"(으)로 설정되어 있지 않습니다.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini 파일에서 설정을 변경하면 Nextcloud가 다시 실행됩니다", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 값이 \"%s\"(으)로 설정되어 있으나 \"0\"으로 설정해야 함", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "이 문제를 해결하려면 php.ini에서 mbstring.func_overload 값을 0으로 설정하십시오", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 이상이 필요합니다. 현재 버전은 %s입니다.", + "To fix this issue update your libxml2 version and restart your web server." : "이 문제를 해결하려면 libxml2 버전을 업데이트하고 웹 서버를 다시 시작하십시오.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP에서 인라인 문서 블록을 삭제하도록 설정되어 있습니다. 일부 코어 앱을 사용하지 못할 수도 있습니다.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Zend OPcache, eAccelerator 같은 캐시/가속기 문제일 수도 있습니다.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP 모듈이 설치되었지만 여전히 없는 것으로 나타납니까?", + "Please ask your server administrator to restart the web server." : "서버 관리자에게 웹 서버 재시작을 요청하십시오.", + "PostgreSQL >= 9 required" : "PostgreSQL 버전 9 이상이 필요합니다", + "Please upgrade your database version" : "데이터베이스 버전을 업그레이드 하십시오", + "Your data directory is readable by other users" : "내 데이터 디렉터리를 다른 사용자가 읽을 수 있음", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "권한을 0770으로 변경하여 다른 사용자가 읽을 수 없도록 하십시오.", + "Your data directory must be an absolute path" : "내 데이터 디렉터리는 절대 경로여야 함", + "Check the value of \"datadirectory\" in your configuration" : "설정 중 \"datadirectory\" 값을 확인하십시오", + "Your data directory is invalid" : "내 데이터 디렉터리가 잘못됨", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "데이터 디렉터리의 최상위 디렉터리에 \".ocdata\" 파일이 있는지 확인하십시오.", + "Authentication failed, wrong token or provider ID given" : "인증이 실패하였습니다. 토큰이나 프로바이더 ID가 틀렸습니다.", + "Could not obtain lock type %d on \"%s\"." : "잠금 형식 %d을(를) \"%s\"에 대해 얻을 수 없습니다.", + "Storage unauthorized. %s" : "저장소가 인증되지 않았습니다. %s", + "Storage incomplete configuration. %s" : "저장소 설정이 완전하지 않습니다. %s", + "Storage connection error. %s" : "저장소 연결 오류입니다. %s", + "Storage is temporarily not available" : "저장소를 임시로 사용할 수 없음", + "Storage connection timeout. %s" : "저장소 연결 시간이 초과되었습니다. %s", + "Following databases are supported: %s" : "다음 데이터베이스를 지원합니다: %s", + "Following platforms are supported: %s" : "다음 플랫폼을 지원합니다: %s", + "Overview" : "개요", + "Basic settings" : "기본 설정", + "Sharing" : "공유", + "Security" : "보안", + "Groupware" : "그룹웨어", + "Personal info" : "개인 정보", + "Mobile & desktop" : "모바일 & 데스크톱", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "apps 디렉터리에 웹 서버의 쓰기 권한을 부여하거나 설정에서 앱 스토어를 비활성화해서 해결할 수 있습니다. %s 문서를 참조하십시오" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ko.json b/docker/overlays/nextcloud/html/lib/l10n/ko.json new file mode 100644 index 0000000..7022fc5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ko.json @@ -0,0 +1,210 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\" 디렉터리에 기록할 수 없습니다!", + "This can usually be fixed by giving the webserver write access to the config directory" : "config 디렉터리에 웹 서버 쓰기 권한을 부여해서 해결할 수 있습니다", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오.", + "See %s" : "%s 보기", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오. %s를 참조하십시오.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "앱 %1$s의 파일이 올바르게 교체되지 않았습니다. 서버와 호환되는 버전인지 확인하십시오.", + "Sample configuration detected" : "예제 설정 감지됨", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "예제 설정이 복사된 것 같습니다. 올바르게 작동하지 않을 수도 있기 때문에 지원되지 않습니다. config.php를 변경하기 전 문서를 읽어 보십시오", + "%1$s and %2$s" : "%1$s 및 %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s 및 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s 및 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s 및 %5$s", + "Education Edition" : "교육용 에디션", + "Enterprise bundle" : "엔터프라이즈 번들", + "Groupware bundle" : "그룹웨어 번들", + "Social sharing bundle" : "소셜 공유 번들", + "PHP %s or higher is required." : "PHP 버전 %s 이상이 필요합니다.", + "PHP with a version lower than %s is required." : "PHP 버전 %s 미만이 필요합니다.", + "%sbit or higher PHP required." : "%s비트 이상의 PHP가 필요합니다.", + "The command line tool %s could not be found" : "명령행 도구 %s을(를) 찾을 수 없습니다", + "The library %s is not available." : "%s 라이브러리를 사용할 수 없습니다.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.", + "Server version %s or higher is required." : "서버 버전 %s 이상이 필요합니다.", + "Server version %s or lower is required." : "서버 버전 %s 미만이 필요합니다.", + "Logged in user must be an admin or sub admin" : "로그인한 사용자는 관리자 또는 부 관리자여야 합니다.", + "Logged in user must be an admin" : "로그인한 사용자는 관리자여야 합니다.", + "Wiping of device %s has started" : "디바이스 %s의 완전 삭제가 시작되었습니다.", + "Wiping of device »%s« has started" : "디바이스 »%s«의 완전 삭제가 시작되었습니다.", + "Authentication" : "인증", + "Unknown filetype" : "알 수 없는 파일 형식", + "Invalid image" : "잘못된 사진", + "Avatar image is not square" : "아바타 사진이 정사각형이 아님", + "today" : "오늘", + "tomorrow" : "내일", + "yesterday" : "어제", + "_in %n day_::_in %n days_" : ["%n일 후"], + "_%n day ago_::_%n days ago_" : ["%n일 전"], + "next month" : "다음 달", + "last month" : "지난 달", + "_in %n month_::_in %n months_" : ["%n개월 후"], + "_%n month ago_::_%n months ago_" : ["%n개월 전 "], + "next year" : "내년", + "last year" : "작년", + "_in %n year_::_in %n years_" : ["%n년 후"], + "_%n year ago_::_%n years ago_" : ["%n년 전"], + "_in %n hour_::_in %n hours_" : ["%n시간 후"], + "_%n hour ago_::_%n hours ago_" : ["%n시간 전"], + "_in %n minute_::_in %n minutes_" : ["%n분 후"], + "_%n minute ago_::_%n minutes ago_" : ["%n분 전"], + "in a few seconds" : "몇 초 후", + "seconds ago" : "초 전", + "Empty file" : "빈 파일", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %s인 모듈이 존재하지 않습니다. 앱 설정에서 확인하거나 시스템 관리자에게 연락하십시오.", + "File name is a reserved word" : "파일 이름이 예약된 단어임", + "File name contains at least one invalid character" : "파일 이름에 잘못된 글자가 한 자 이상 있음", + "File name is too long" : "파일 이름이 너무 김", + "Dot files are not allowed" : "점으로 시작하는 파일은 허용되지 않음", + "Empty filename is not allowed" : "파일 이름을 비워 둘 수 없음", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfo 파일을 읽을 수 없어서 앱 \"%s\"을(를) 설치할 수 없습니다.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "이 서버 버전과 호환되지 않아서 앱 \"%s\"을(를) 설치할 수 없습니다", + "__language_name__" : "한국어", + "This is an automatically sent email, please do not reply." : "자동으로 전송한 이메일입니다. 답장하지 마십시오.", + "Help" : "도움말", + "Apps" : "앱", + "Settings" : "설정", + "Log out" : "로그아웃", + "Users" : "사용자", + "Unknown user" : "알려지지 않은 사용자", + "Additional settings" : "고급 설정", + "%s enter the database username and name." : "%s 데이터베이스 사용자 이름과 이름을 입력해 주십시오.", + "%s enter the database username." : "%s 데이터베이스 사용자 이름을 입력해 주십시오.", + "%s enter the database name." : "%s 데이터베이스 이름을 입력하십시오.", + "%s you may not use dots in the database name" : "%s 데이터베이스 이름에는 마침표를 사용할 수 없습니다", + "You need to enter details of an existing account." : "존재하는 계정 정보를 입력해야 합니다.", + "Oracle connection could not be established" : "Oracle 연결을 수립할 수 없습니다.", + "Oracle username and/or password not valid" : "Oracle 사용자 이름이나 암호가 잘못되었습니다.", + "PostgreSQL username and/or password not valid" : "PostgreSQL 사용자 이름 또는 암호가 잘못되었습니다", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X은 지원하지 않으며 %s이(가) 이 플랫폼에서 올바르게 작동하지 않을 수도 있습니다. 본인 책임으로 사용하십시오! ", + "For the best results, please consider using a GNU/Linux server instead." : "더 좋은 결과를 얻으려면 GNU/Linux 서버를 사용하는 것을 권장합니다.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "%s 인스턴스가 32비트 PHP 환경에서 실행 중이고 php.ini에 open_basedir이 설정되어 있습니다. 4GB 이상의 파일 처리에 문제가 생길 수 있으므로 추천하지 않습니다.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini의 open_basedir 설정을 삭제하거나 64비트 PHP로 전환하십시오.", + "Set an admin username." : "관리자의 사용자 이름을 설정합니다.", + "Set an admin password." : "관리자의 암호를 설정합니다.", + "Can't create or write into the data directory %s" : "데이터 디렉터리 %s을(를) 만들거나 기록할 수 없음", + "Invalid Federated Cloud ID" : "잘못된 연합 클라우드 ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "공유 백엔드 %s에서 OCP\\Share_Backend 인터페이스를 구현해야 함", + "Sharing backend %s not found" : "공유 백엔드 %s을(를) 찾을 수 없음", + "Sharing backend for %s not found" : "%s의 공유 백엔드를 찾을 수 없음", + "Open »%s«" : "%s 열기", + "%1$s via %2$s" : "%1$s(%2$s 경유)", + "You are not allowed to share %s" : "%s을(를) 공유할 수 있는 권한이 없습니다", + "Can’t increase permissions of %s" : "%s의 권한을 늘릴 수 없습니다", + "Files can’t be shared with delete permissions" : "파일을 삭제 권한으로 공유할 수 없습니다", + "Files can’t be shared with create permissions" : "파일을 생성 권한으로 공유할 수 없습니다", + "Expiration date is in the past" : "만료 날짜가 과거입니다", + "Can’t set expiration date more than %s days in the future" : "만료 날짜를 %s일 이상 이후로 설정할 수 없습니다", + "Click the button below to open it." : "아래 단추를 눌러서 열 수 있습니다.", + "The requested share does not exist anymore" : "요청한 공유가 더 이상 존재하지 않습니다", + "Could not find category \"%s\"" : "분류 \"%s\"을(를) 찾을 수 없습니다", + "Sunday" : "일요일", + "Monday" : "월요일", + "Tuesday" : "화요일", + "Wednesday" : "수요일", + "Thursday" : "목요일", + "Friday" : "금요일", + "Saturday" : "토요일", + "Sun." : "일", + "Mon." : "월", + "Tue." : "화", + "Wed." : "수", + "Thu." : "목", + "Fri." : "금", + "Sat." : "토", + "Su" : "일", + "Mo" : "월", + "Tu" : "화", + "We" : "수", + "Th" : "목", + "Fr" : "금", + "Sa" : "토", + "January" : "1월", + "February" : "2월", + "March" : "3월", + "April" : "4월", + "May" : "5월", + "June" : "6월", + "July" : "7월", + "August" : "8월", + "September" : "9월", + "October" : "10월", + "November" : "11월", + "December" : "12월", + "Jan." : "1월", + "Feb." : "2월", + "Mar." : "3월", + "Apr." : "4월", + "May." : "5월", + "Jun." : "6월", + "Jul." : "7월", + "Aug." : "8월", + "Sep." : "9월", + "Oct." : "10월", + "Nov." : "11월", + "Dec." : "12월", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "다음 문자만 이름에 사용할 수 있습니다: \"a-z\", \"A-Z\", \"0-9\", 및 \"_.@-'\"", + "A valid username must be provided" : "올바른 사용자 이름을 입력해야 합니다", + "Username contains whitespace at the beginning or at the end" : "사용자 이름의 시작이나 끝에 공백이 있습니다", + "Username must not consist of dots only" : "사용자 이름에 마침표만 있으면 안 됩니다", + "Username is invalid because files already exist for this user" : "무효한 사용자이름. 이미 존재함", + "A valid password must be provided" : "올바른 암호를 입력해야 합니다", + "The username is already being used" : "사용자 이름이 이미 존재합니다", + "Could not create user" : "사용자를 만들 수 없습니다", + "User disabled" : "사용자 비활성화됨", + "Login canceled by app" : "앱에서 로그인 취소함", + "a safe home for all your data" : "내 모든 데이터의 안전한 저장소", + "File is currently busy, please try again later" : "파일이 현재 사용 중, 나중에 다시 시도하십시오", + "Can't read file" : "파일을 읽을 수 없음", + "Application is not enabled" : "앱이 활성화되지 않았습니다", + "Authentication error" : "인증 오류", + "Token expired. Please reload page." : "토큰이 만료되었습니다. 페이지를 새로 고치십시오.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "데이터베이스 드라이버(sqlite, mysql, postgresql)가 설치되지 않았습니다.", + "Cannot write into \"config\" directory" : "\"config\" 디렉터리에 기록할 수 없습니다", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "config 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오", + "Cannot write into \"apps\" directory" : "\"apps\" 디렉터리에 기록할 수 없습니다", + "Cannot create \"data\" directory" : "\"data\" 디렉터리를 만들 수 없음", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "루트 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "루트 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 문서를 참조하십시오.", + "Setting locale to %s failed" : "로캘을 %s(으)로 설정할 수 없음", + "Please install one of these locales on your system and restart your webserver." : "다음 중 하나 이상의 로캘을 시스템에 설치하고 웹 서버를 다시 시작하십시오.", + "PHP module %s not installed." : "PHP 모듈 %s이(가) 설치되지 않았습니다.", + "Please ask your server administrator to install the module." : "서버 관리자에게 모듈 설치를 요청하십시오.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 설정 \"%s\"이(가) \"%s\"(으)로 설정되어 있지 않습니다.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini 파일에서 설정을 변경하면 Nextcloud가 다시 실행됩니다", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 값이 \"%s\"(으)로 설정되어 있으나 \"0\"으로 설정해야 함", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "이 문제를 해결하려면 php.ini에서 mbstring.func_overload 값을 0으로 설정하십시오", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 이상이 필요합니다. 현재 버전은 %s입니다.", + "To fix this issue update your libxml2 version and restart your web server." : "이 문제를 해결하려면 libxml2 버전을 업데이트하고 웹 서버를 다시 시작하십시오.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP에서 인라인 문서 블록을 삭제하도록 설정되어 있습니다. 일부 코어 앱을 사용하지 못할 수도 있습니다.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Zend OPcache, eAccelerator 같은 캐시/가속기 문제일 수도 있습니다.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP 모듈이 설치되었지만 여전히 없는 것으로 나타납니까?", + "Please ask your server administrator to restart the web server." : "서버 관리자에게 웹 서버 재시작을 요청하십시오.", + "PostgreSQL >= 9 required" : "PostgreSQL 버전 9 이상이 필요합니다", + "Please upgrade your database version" : "데이터베이스 버전을 업그레이드 하십시오", + "Your data directory is readable by other users" : "내 데이터 디렉터리를 다른 사용자가 읽을 수 있음", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "권한을 0770으로 변경하여 다른 사용자가 읽을 수 없도록 하십시오.", + "Your data directory must be an absolute path" : "내 데이터 디렉터리는 절대 경로여야 함", + "Check the value of \"datadirectory\" in your configuration" : "설정 중 \"datadirectory\" 값을 확인하십시오", + "Your data directory is invalid" : "내 데이터 디렉터리가 잘못됨", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "데이터 디렉터리의 최상위 디렉터리에 \".ocdata\" 파일이 있는지 확인하십시오.", + "Authentication failed, wrong token or provider ID given" : "인증이 실패하였습니다. 토큰이나 프로바이더 ID가 틀렸습니다.", + "Could not obtain lock type %d on \"%s\"." : "잠금 형식 %d을(를) \"%s\"에 대해 얻을 수 없습니다.", + "Storage unauthorized. %s" : "저장소가 인증되지 않았습니다. %s", + "Storage incomplete configuration. %s" : "저장소 설정이 완전하지 않습니다. %s", + "Storage connection error. %s" : "저장소 연결 오류입니다. %s", + "Storage is temporarily not available" : "저장소를 임시로 사용할 수 없음", + "Storage connection timeout. %s" : "저장소 연결 시간이 초과되었습니다. %s", + "Following databases are supported: %s" : "다음 데이터베이스를 지원합니다: %s", + "Following platforms are supported: %s" : "다음 플랫폼을 지원합니다: %s", + "Overview" : "개요", + "Basic settings" : "기본 설정", + "Sharing" : "공유", + "Security" : "보안", + "Groupware" : "그룹웨어", + "Personal info" : "개인 정보", + "Mobile & desktop" : "모바일 & 데스크톱", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "apps 디렉터리에 웹 서버의 쓰기 권한을 부여하거나 설정에서 앱 스토어를 비활성화해서 해결할 수 있습니다. %s 문서를 참조하십시오" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/lb.js b/docker/overlays/nextcloud/html/lib/l10n/lb.js new file mode 100644 index 0000000..0c9bcc2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lb.js @@ -0,0 +1,70 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Keng Schreiwreschter op d' 'config' Verzeechnis!", + "See %s" : "%s kucken", + "Sample configuration detected" : "Beispill-Konfiguratioun erkannt", + "PHP %s or higher is required." : "PHP %s oder méi nei ass néideg.", + "PHP with a version lower than %s is required." : "PHP mat enger Versioun %s oder méi kleng ass néideg.", + "Unknown filetype" : "Onbekannten Fichier Typ", + "Invalid image" : "Ongülteg d'Bild", + "today" : "haut", + "yesterday" : "gëschter", + "_%n day ago_::_%n days ago_" : ["%n Dag hier","%n Deeg hier"], + "last month" : "Läschte Mount", + "_%n month ago_::_%n months ago_" : ["%n Mount hier","%n Méint hier"], + "last year" : "Läscht Joer", + "_%n year ago_::_%n years ago_" : ["%n Joer hier","%n Joer hier"], + "_%n hour ago_::_%n hours ago_" : ["%n Stonn hier","%n Stonnen hier"], + "seconds ago" : "Sekonnen hir", + "__language_name__" : "Lëtzebuergesch", + "Help" : "Hëllef", + "Apps" : "Applikatiounen", + "Settings" : "Astellungen", + "Log out" : "Ofmellen", + "Users" : "Benotzer", + "Set an admin password." : "Admin Passwuert setzen", + "Sunday" : "Sonndeg", + "Monday" : "Méindeg", + "Tuesday" : "Dënschdeg", + "Wednesday" : "Mëttwoch", + "Thursday" : "Donneschdeg", + "Friday" : "Freideg", + "Saturday" : "Samschdeg", + "Sun." : "So. ", + "Mon." : "Méi.", + "Tue." : "Dë.", + "Wed." : "Më.", + "Thu." : "Do.", + "Fri." : "Fr.", + "Sat." : "Sa.", + "January" : "Januar", + "February" : "Februar", + "March" : "Mäerz", + "April" : "Abrëll", + "May" : "Mee", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mäe.", + "Apr." : "Abr.", + "May." : "Mee", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Authentication error" : "Authentifikatioun's Fehler", + "Storage is temporarily not available" : "Späicherplaatz temporär net erreeschbar", + "Following databases are supported: %s" : "Dës Datebanke ginn ënnerstëtzt: %s", + "Sharing" : "Gedeelt" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/lb.json b/docker/overlays/nextcloud/html/lib/l10n/lb.json new file mode 100644 index 0000000..d8981f2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lb.json @@ -0,0 +1,68 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Keng Schreiwreschter op d' 'config' Verzeechnis!", + "See %s" : "%s kucken", + "Sample configuration detected" : "Beispill-Konfiguratioun erkannt", + "PHP %s or higher is required." : "PHP %s oder méi nei ass néideg.", + "PHP with a version lower than %s is required." : "PHP mat enger Versioun %s oder méi kleng ass néideg.", + "Unknown filetype" : "Onbekannten Fichier Typ", + "Invalid image" : "Ongülteg d'Bild", + "today" : "haut", + "yesterday" : "gëschter", + "_%n day ago_::_%n days ago_" : ["%n Dag hier","%n Deeg hier"], + "last month" : "Läschte Mount", + "_%n month ago_::_%n months ago_" : ["%n Mount hier","%n Méint hier"], + "last year" : "Läscht Joer", + "_%n year ago_::_%n years ago_" : ["%n Joer hier","%n Joer hier"], + "_%n hour ago_::_%n hours ago_" : ["%n Stonn hier","%n Stonnen hier"], + "seconds ago" : "Sekonnen hir", + "__language_name__" : "Lëtzebuergesch", + "Help" : "Hëllef", + "Apps" : "Applikatiounen", + "Settings" : "Astellungen", + "Log out" : "Ofmellen", + "Users" : "Benotzer", + "Set an admin password." : "Admin Passwuert setzen", + "Sunday" : "Sonndeg", + "Monday" : "Méindeg", + "Tuesday" : "Dënschdeg", + "Wednesday" : "Mëttwoch", + "Thursday" : "Donneschdeg", + "Friday" : "Freideg", + "Saturday" : "Samschdeg", + "Sun." : "So. ", + "Mon." : "Méi.", + "Tue." : "Dë.", + "Wed." : "Më.", + "Thu." : "Do.", + "Fri." : "Fr.", + "Sat." : "Sa.", + "January" : "Januar", + "February" : "Februar", + "March" : "Mäerz", + "April" : "Abrëll", + "May" : "Mee", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Dezember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mäe.", + "Apr." : "Abr.", + "May." : "Mee", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Authentication error" : "Authentifikatioun's Fehler", + "Storage is temporarily not available" : "Späicherplaatz temporär net erreeschbar", + "Following databases are supported: %s" : "Dës Datebanke ginn ënnerstëtzt: %s", + "Sharing" : "Gedeelt" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/lt_LT.js b/docker/overlays/nextcloud/html/lib/l10n/lt_LT.js new file mode 100644 index 0000000..5c5ef52 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lt_LT.js @@ -0,0 +1,210 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nepavyksta rašyti į \"config\" katalogą!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tai, dažniausiai, gali būti ištaisyta suteikiant saityno serveriui rašymo prieigą prie konfigūracijos katalogo", + "See %s" : "Žiūrėkite %s", + "Sample configuration detected" : "Aptiktas konfigūracijos pavyzdys", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pastebėta, kad nukopijuota pavyzdinė konfigūracija. Tai gali pažeisti jūsų diegimą ir yra nepalaikoma. Prieš atliekant pakeitimus config.php faile, prašome perskaityti dokumentaciją.", + "%1$s and %2$s" : "%1$s ir %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ir %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ir %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ir %5$s", + "Groupware bundle" : "Grupinio darbo įrangos rinkinys", + "PHP %s or higher is required." : "Reikalinga PHP %s arba aukštesnė.", + "PHP with a version lower than %s is required." : "Reikalinga žemesnė nei %s PHP versija. ", + "The command line tool %s could not be found" : "Nepavyko rasti komandų eilutės įrankio %s", + "The library %s is not available." : "Biblioteka %s nėra prieinama.", + "Server version %s or higher is required." : "Reikalinga %s arba aukštesnė serverio versija ", + "Server version %s or lower is required." : "Reikalinga %s arba žemesnė serverio versija. ", + "Logged in user must be an admin" : "Prisijungęs naudotojas privalo būti administratoriumi", + "Wiping of device %s has started" : "Įrenginio %s duomenų ištrynimas pradėtas", + "Wiping of device »%s« has started" : "Įrenginio »%s« duomenų ištrynimas pradėtas", + "»%s« started remote wipe" : "»%s« pradėjo nuotolinių duomenų ištrynimą", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Įrenginys ar programa »%s« pradėjo nuotolinių duomenų ištrynimo procesą. Procesui užsibaigus, gausite dar vieną el. laišką", + "Wiping of device %s has finished" : "Įrenginio %s duomenų ištrynimas užbaigtas", + "Wiping of device »%s« has finished" : "Įrenginio »%s« duomenų ištrynimas užbaigtas", + "»%s« finished remote wipe" : "»%s« užbaigė nuotolinių duomenų ištrynimą", + "Device or application »%s« has finished the remote wipe process." : "Įrenginys ar programa »%s« užbaigė nuotolinių duomenų ištrynimo procesą.", + "Remote wipe started" : "Nuotolinių duomenų ištrynimas pradėtas", + "A remote wipe was started on device %s" : "Nuotolinių duomenų ištrynimas buvo pradėtas įrenginyje %s", + "Remote wipe finished" : "Nuotolinių duomenų ištrynimas užbaigtas", + "The remote wipe on %s has finished" : "Nuotolinių duomenų ištrynimas ties %s yra užbaigtas", + "Authentication" : "Tapatybės nustatymas", + "Unknown filetype" : "Nežinomas failo tipas", + "Invalid image" : "Neteisingas paveikslas", + "Avatar image is not square" : "Avataro paveikslas nėra kvadratinis", + "today" : "šiandien", + "tomorrow" : "rytoj", + "yesterday" : "vakar", + "_in %n day_::_in %n days_" : ["po %n dienos","po %n dienų","po %n dienų","po %n dienos"], + "_%n day ago_::_%n days ago_" : ["prieš %n dieną","prieš %n dienas","prieš %n dienų","prieš %n dieną"], + "last month" : "praeitą mėnesį", + "_in %n month_::_in %n months_" : ["po %n mėnesio","po %n mėnesių","po %n mėnesių","po %n mėnesio"], + "_%n month ago_::_%n months ago_" : ["prieš %n mėnesį","prieš %n mėnesius","prieš %n mėnesių","prieš %n mėnesį"], + "last year" : "praeitais metais", + "_in %n year_::_in %n years_" : ["po %n metų","po %n metų","po %n metų","po %n metų"], + "_%n year ago_::_%n years ago_" : ["prieš %n metus","prieš %n metus","prieš %n metų","prieš %n metus"], + "_in %n hour_::_in %n hours_" : ["po %n valandos","po %n valandų","po %n valandų","po %n valandos"], + "_%n hour ago_::_%n hours ago_" : ["prieš %n valandą","prieš %n valandas","prieš %n valandų","prieš %n valandą"], + "_in %n minute_::_in %n minutes_" : ["po %n minutės","po %n minučių","po %n minučių","po %n minutės"], + "_%n minute ago_::_%n minutes ago_" : ["prieš %n minutę","prieš %n minutes","prieš %n minučių","prieš %n minutę"], + "in a few seconds" : "po kelių sekundžių", + "seconds ago" : "prieš keletą sekundžių", + "Empty file" : "Tuščias failas", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulio, kurio ID: %s, nėra. Prašome jį įjungti savo programėlių nustatymuose arba susisiekti su savo administratoriumi.", + "File name is a reserved word" : "Failo pavadinimas negalimas, žodis rezervuotas", + "File name contains at least one invalid character" : "Failo vardas sudarytas iš neleistinų simbolių", + "File name is too long" : "Failo pavadinimas per ilgas", + "Empty filename is not allowed" : "Tuščias failo pavadinimas nėra leidžiamas", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Programėlė \"%s\" negali būti įdiegta, kadangi negalima perskaityti appinfo failo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Programėlė \"%s\" negali būti įdiegta, kadangi ji nėra suderinama su serverio versija.", + "__language_name__" : "Lietuvių", + "This is an automatically sent email, please do not reply." : "Tai yra automatinis pranešimas, prašome neatsakyti.", + "Help" : "Pagalba", + "Apps" : "Programėlės", + "Settings" : "Nustatymai", + "Log out" : "Atsijungti", + "Users" : "Naudotojai", + "Unknown user" : "Nežinomas naudotojas", + "Additional settings" : "Papildomi nustatymai", + "%s enter the database username and name." : "%s įrašykite duomenų bazės naudotojo vardą ir pavadinimą.", + "%s enter the database username." : "%s įrašykite duomenų bazės naudotojo vardą.", + "%s enter the database name." : "%s įrašykite duomenų bazės pavadinimą.", + "%s you may not use dots in the database name" : "%s negalite naudoti taškų duombazės pavadinime", + "MySQL username and/or password not valid" : "Neteisingas MySQL naudotojo vardas ir/arba slaptažodis", + "You need to enter details of an existing account." : "Jūs turite suvesti egzistuojančios paskyros duomenis.", + "Oracle connection could not be established" : "Nepavyko užmegzti Oracle ryšio", + "Oracle username and/or password not valid" : "Neteisingas Oracle naudotojo vardas ir/arba slaptažodis", + "PostgreSQL username and/or password not valid" : "Neteisingas PostgreSQL naudotojo vardas ir/arba slaptažodis", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nėra palaikomas, %s neveiks tinkamai šioje platformoje. Naudodami prisiimate visą riziką !", + "For the best results, please consider using a GNU/Linux server instead." : "Geriausiems rezultatams, apsvarstykite galimybę, vietoj šio, naudoti GNU/Linux serverį", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Pašalinkite savo php.ini faile open_basedir nustatymą arba persijunkite į 64-bitų PHP.", + "Set an admin username." : "Nustatyti administratoriaus naudotojo vardą.", + "Set an admin password." : "Nustatyti administratoriaus slaptažodį.", + "Can't create or write into the data directory %s" : "Negalima nuskaityti arba rašyti į duomenų katalogą. %s", + "Invalid Federated Cloud ID" : "Neteisingas federacinės debesijos ID", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s pasidalino „%2$s“ su jumis ir parašė pastabą:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s pasidalino „%2$s“ su jumis ir parašė pastabą", + "»%s« added a note to a file shared with you" : "„%s“ parašė pastabą su jumis pasidalintam failui", + "Open »%s«" : "Atverti \"%s\"", + "%1$s via %2$s" : "%1$s per %2$s", + "You are not allowed to share %s" : "Jums neleidžiama bendrinti %s", + "Can’t increase permissions of %s" : "Negalima pridėti papildomų %s leidimų", + "Files can’t be shared with delete permissions" : "Failai negali būti bendrinami su trynimo leidimu.", + "Files can’t be shared with create permissions" : "Failai negali būti bendrinami su sukūrimo leidimu.", + "Expiration date is in the past" : "Bendrinimo pabaigos data yra praėjęs laikas", + "Can’t set expiration date more than %s days in the future" : "Negalima nustatyti galiojimo laiko ilgesnio nei %s dienos.", + "%1$s shared »%2$s« with you" : "%1$s pasidalino „%2$s“ su jumis", + "%1$s shared »%2$s« with you." : "%1$s pasidalino „%2$s“ su jumis.", + "Click the button below to open it." : "Norėdami atverti failą, spustelėkite mygtuką žemiau.", + "The requested share does not exist anymore" : "Pageidaujamas bendrinimas daugiau neegzistuoja.", + "Could not find category \"%s\"" : "Nepavyko rasti kategorijos „%s“", + "Sunday" : "Sekmadienis", + "Monday" : "Pirmadienis", + "Tuesday" : "Antradienis", + "Wednesday" : "Trečiadienis", + "Thursday" : "Ketvirtadienis", + "Friday" : "Penktadienis", + "Saturday" : "Šeštadienis", + "Sun." : "Sek.", + "Mon." : "Pir.", + "Tue." : "Ant.", + "Wed." : "Tre.", + "Thu." : "Ket.", + "Fri." : "Pen.", + "Sat." : "Šeš.", + "Su" : "Sk", + "Mo" : "Pr", + "Tu" : "An", + "We" : "Tr", + "Th" : "Kt", + "Fr" : "Pn", + "Sa" : "Št", + "January" : "Sausis", + "February" : "Vasaris", + "March" : "Kovas", + "April" : "Balandis", + "May" : "Gegužė", + "June" : "Birželis", + "July" : "Liepa", + "August" : "Rugpjūtis", + "September" : "Rugsėjis", + "October" : "Spalis", + "November" : "Lapkritis", + "December" : "Gruodis", + "Jan." : "Sau.", + "Feb." : "Vas.", + "Mar." : "Kov.", + "Apr." : "Bal.", + "May." : "Geg.", + "Jun." : "Bir.", + "Jul." : "Lie.", + "Aug." : "Rgp.", + "Sep." : "Rgs.", + "Oct." : "Spl.", + "Nov." : "Lap.", + "Dec." : "Grd.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Naudotojo varde leidžiama naudoti tik šiuos simbolius: \"a-z\", \"A-Z\", \"0-9\", ir \"_.@-'\"", + "A valid username must be provided" : "Privalo būti pateiktas tinkamas naudotojo vardas", + "Username contains whitespace at the beginning or at the end" : "Naudotojo varde pradžioje ar pabaigoje yra tarpas", + "Username must not consist of dots only" : "Naudotojo vardas negali būti sudarytas tik iš taškų.", + "A valid password must be provided" : "Slaptažodis turi būti tinkamas", + "The username is already being used" : "Naudotojo vardas jau yra naudojamas", + "Could not create user" : "Nepavyko sukurti naudotojo", + "User disabled" : "Naudotojas išjungtas", + "Login canceled by app" : "Programėlė nutraukė prisijungimo procesą", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programėlė \"%1$s\" negali būti įdiegta, nes nėra patenkinamos šios priklausomybės: %2$s", + "a safe home for all your data" : "saugūs namai visiems jūsų duomenims", + "File is currently busy, please try again later" : "Failas šiuo metu yra užimtas, prašome vėliau pabandyti dar kartą", + "Can't read file" : "Nepavyksta perskaityti failo", + "Application is not enabled" : "Programa neįjungta", + "Authentication error" : "Tapatybės nustatymo klaida", + "Token expired. Please reload page." : "Pasibaigė prieigos rakto galiojimas. Prašome įkelti puslapį iš naujo.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nėra įdiegtos duomenų bazių tvarkyklės (sqlite, mysql, or postgresql)", + "Cannot write into \"config\" directory" : "Nepavyksta rašyti į \"config\" katalogą!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Tai, dažniausiai, gali būti pataisyta, suteikiant saityno serveriui rašymo prieigą prie konfigūracijos katalogo. Žiūrėkite %s", + "Cannot write into \"apps\" directory" : "Nepavyksta įrašyti į \"apps\" katalogą", + "Cannot create \"data\" directory" : "Nepavyksta sukurti katalogo \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Tai, dažniausiai, gali būti ištaisyta, suteikiant saityno serveriui rašymo prieigą prie šakninio katalogo. Žiūrėkite %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Teisės gali būti pataisytos, suteikiant saityno serveriui rašymo prieigą prie šakninio katalogo. Žiūrėkite %s", + "Setting locale to %s failed" : "Nepavyko nustatyti vietos %s", + "Please install one of these locales on your system and restart your webserver." : "Prašome įdiekite vieną šių lokalių savo sistemoje ir perkraukite žiniatinklio serverį.", + "PHP module %s not installed." : "PHP modulis %s neįdiegtas.", + "Please ask your server administrator to install the module." : "Kreipkitės į savo sistemos administratorių, kad jis įdiegtų modulį.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP nustatymas \"%s\" nenustatytas į \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Šių nustatymų pritaikymas php.ini faile, iš naujo paleis Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload yra nustatytas į \"%s\" vietoj numatomos reikšmės \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Norėdami išspręsti šią problemą, nustatykite mbstring.func_overload į 0 savo php.ini faile", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Reikalinga ne mažesnė nei libxml2 2.7.0 versija. Šiuo metu yra instaliuota %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Atnaujinkite libxml2 versiją ir perkraukite žiniatinklio serverį, kad sutvarkytumėte šią problemą.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduliai yra įdiegti, bet jų vis tiek trūksta?", + "Please ask your server administrator to restart the web server." : "Kreipkitės į savo sistemos administratorių, kad jis perkrautų žiniatinklio serverį.", + "PostgreSQL >= 9 required" : "Reikalinga PostgreSQL >= 9", + "Please upgrade your database version" : "Atnaujinkite savo duomenų bazės versiją", + "Your data directory is readable by other users" : "Duomenų katalogą gali skaityti kiti naudotojai", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Pakeiskite leidimus į 0770, kad šis katalogas negalėtų būti išvardytas kitiems naudotojams.", + "Your data directory must be an absolute path" : "Jūsų duomenų katalogas privalo būti absoliutusis kelias", + "Check the value of \"datadirectory\" in your configuration" : "Patikrinkite \"datadirectory\" reikšmę savo konfigūracijoje.", + "Your data directory is invalid" : "Neteisingas duomenų katalogas", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Įsitikinkite, kad šakniniame duomenų kataloge yra yra \".ocdata\" failas.", + "Action \"%s\" not supported or implemented." : "Veiksmas \"%s\" nepalaikomas ar neįgyvendintas.", + "Authentication failed, wrong token or provider ID given" : "Tapatybės nustatymas nepavyko, nurodytas neteisingas prieigos raktas arba teikėjo ID", + "Could not obtain lock type %d on \"%s\"." : "Nepavyko gauti užrakto tipo %d ties \"%s\".", + "Storage unauthorized. %s" : "Saugykla nesankcionuota. %s", + "Storage incomplete configuration. %s" : "Nepilna saugyklos konfigūracija. %s", + "Storage connection error. %s" : "Saugyklos sujungimo ryšio klaida. %s", + "Storage is temporarily not available" : "Saugykla yra laikinai neprieinama", + "Storage connection timeout. %s" : "Sujungimo su saugykla laikas baigėsi. %s", + "Following databases are supported: %s" : "Yra palaikomos šios duomenų bazės: %s", + "Following platforms are supported: %s" : "Yra palaikomos šios platformos: %s", + "Overview" : "Apžvalga", + "Basic settings" : "Pagrindiniai nustatymai", + "Sharing" : "Bendrinimas", + "Security" : "Saugumas", + "Groupware" : "Grupinio darbo įranga", + "Personal info" : "Asmeninė informacija", + "Mobile & desktop" : "Mobilieji ir darbalaukiai", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Tai, dažniausiai, gali būti ištaisyta, suteikiant saityno serveriui rašymo prieigą prie programų katalogo arba uždraudžiant appstore konfigūraciniame kataloge. Žiūrėkite %s" +}, +"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/lt_LT.json b/docker/overlays/nextcloud/html/lib/l10n/lt_LT.json new file mode 100644 index 0000000..fd6c7c1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lt_LT.json @@ -0,0 +1,208 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nepavyksta rašyti į \"config\" katalogą!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Tai, dažniausiai, gali būti ištaisyta suteikiant saityno serveriui rašymo prieigą prie konfigūracijos katalogo", + "See %s" : "Žiūrėkite %s", + "Sample configuration detected" : "Aptiktas konfigūracijos pavyzdys", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pastebėta, kad nukopijuota pavyzdinė konfigūracija. Tai gali pažeisti jūsų diegimą ir yra nepalaikoma. Prieš atliekant pakeitimus config.php faile, prašome perskaityti dokumentaciją.", + "%1$s and %2$s" : "%1$s ir %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ir %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ir %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ir %5$s", + "Groupware bundle" : "Grupinio darbo įrangos rinkinys", + "PHP %s or higher is required." : "Reikalinga PHP %s arba aukštesnė.", + "PHP with a version lower than %s is required." : "Reikalinga žemesnė nei %s PHP versija. ", + "The command line tool %s could not be found" : "Nepavyko rasti komandų eilutės įrankio %s", + "The library %s is not available." : "Biblioteka %s nėra prieinama.", + "Server version %s or higher is required." : "Reikalinga %s arba aukštesnė serverio versija ", + "Server version %s or lower is required." : "Reikalinga %s arba žemesnė serverio versija. ", + "Logged in user must be an admin" : "Prisijungęs naudotojas privalo būti administratoriumi", + "Wiping of device %s has started" : "Įrenginio %s duomenų ištrynimas pradėtas", + "Wiping of device »%s« has started" : "Įrenginio »%s« duomenų ištrynimas pradėtas", + "»%s« started remote wipe" : "»%s« pradėjo nuotolinių duomenų ištrynimą", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Įrenginys ar programa »%s« pradėjo nuotolinių duomenų ištrynimo procesą. Procesui užsibaigus, gausite dar vieną el. laišką", + "Wiping of device %s has finished" : "Įrenginio %s duomenų ištrynimas užbaigtas", + "Wiping of device »%s« has finished" : "Įrenginio »%s« duomenų ištrynimas užbaigtas", + "»%s« finished remote wipe" : "»%s« užbaigė nuotolinių duomenų ištrynimą", + "Device or application »%s« has finished the remote wipe process." : "Įrenginys ar programa »%s« užbaigė nuotolinių duomenų ištrynimo procesą.", + "Remote wipe started" : "Nuotolinių duomenų ištrynimas pradėtas", + "A remote wipe was started on device %s" : "Nuotolinių duomenų ištrynimas buvo pradėtas įrenginyje %s", + "Remote wipe finished" : "Nuotolinių duomenų ištrynimas užbaigtas", + "The remote wipe on %s has finished" : "Nuotolinių duomenų ištrynimas ties %s yra užbaigtas", + "Authentication" : "Tapatybės nustatymas", + "Unknown filetype" : "Nežinomas failo tipas", + "Invalid image" : "Neteisingas paveikslas", + "Avatar image is not square" : "Avataro paveikslas nėra kvadratinis", + "today" : "šiandien", + "tomorrow" : "rytoj", + "yesterday" : "vakar", + "_in %n day_::_in %n days_" : ["po %n dienos","po %n dienų","po %n dienų","po %n dienos"], + "_%n day ago_::_%n days ago_" : ["prieš %n dieną","prieš %n dienas","prieš %n dienų","prieš %n dieną"], + "last month" : "praeitą mėnesį", + "_in %n month_::_in %n months_" : ["po %n mėnesio","po %n mėnesių","po %n mėnesių","po %n mėnesio"], + "_%n month ago_::_%n months ago_" : ["prieš %n mėnesį","prieš %n mėnesius","prieš %n mėnesių","prieš %n mėnesį"], + "last year" : "praeitais metais", + "_in %n year_::_in %n years_" : ["po %n metų","po %n metų","po %n metų","po %n metų"], + "_%n year ago_::_%n years ago_" : ["prieš %n metus","prieš %n metus","prieš %n metų","prieš %n metus"], + "_in %n hour_::_in %n hours_" : ["po %n valandos","po %n valandų","po %n valandų","po %n valandos"], + "_%n hour ago_::_%n hours ago_" : ["prieš %n valandą","prieš %n valandas","prieš %n valandų","prieš %n valandą"], + "_in %n minute_::_in %n minutes_" : ["po %n minutės","po %n minučių","po %n minučių","po %n minutės"], + "_%n minute ago_::_%n minutes ago_" : ["prieš %n minutę","prieš %n minutes","prieš %n minučių","prieš %n minutę"], + "in a few seconds" : "po kelių sekundžių", + "seconds ago" : "prieš keletą sekundžių", + "Empty file" : "Tuščias failas", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulio, kurio ID: %s, nėra. Prašome jį įjungti savo programėlių nustatymuose arba susisiekti su savo administratoriumi.", + "File name is a reserved word" : "Failo pavadinimas negalimas, žodis rezervuotas", + "File name contains at least one invalid character" : "Failo vardas sudarytas iš neleistinų simbolių", + "File name is too long" : "Failo pavadinimas per ilgas", + "Empty filename is not allowed" : "Tuščias failo pavadinimas nėra leidžiamas", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Programėlė \"%s\" negali būti įdiegta, kadangi negalima perskaityti appinfo failo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Programėlė \"%s\" negali būti įdiegta, kadangi ji nėra suderinama su serverio versija.", + "__language_name__" : "Lietuvių", + "This is an automatically sent email, please do not reply." : "Tai yra automatinis pranešimas, prašome neatsakyti.", + "Help" : "Pagalba", + "Apps" : "Programėlės", + "Settings" : "Nustatymai", + "Log out" : "Atsijungti", + "Users" : "Naudotojai", + "Unknown user" : "Nežinomas naudotojas", + "Additional settings" : "Papildomi nustatymai", + "%s enter the database username and name." : "%s įrašykite duomenų bazės naudotojo vardą ir pavadinimą.", + "%s enter the database username." : "%s įrašykite duomenų bazės naudotojo vardą.", + "%s enter the database name." : "%s įrašykite duomenų bazės pavadinimą.", + "%s you may not use dots in the database name" : "%s negalite naudoti taškų duombazės pavadinime", + "MySQL username and/or password not valid" : "Neteisingas MySQL naudotojo vardas ir/arba slaptažodis", + "You need to enter details of an existing account." : "Jūs turite suvesti egzistuojančios paskyros duomenis.", + "Oracle connection could not be established" : "Nepavyko užmegzti Oracle ryšio", + "Oracle username and/or password not valid" : "Neteisingas Oracle naudotojo vardas ir/arba slaptažodis", + "PostgreSQL username and/or password not valid" : "Neteisingas PostgreSQL naudotojo vardas ir/arba slaptažodis", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nėra palaikomas, %s neveiks tinkamai šioje platformoje. Naudodami prisiimate visą riziką !", + "For the best results, please consider using a GNU/Linux server instead." : "Geriausiems rezultatams, apsvarstykite galimybę, vietoj šio, naudoti GNU/Linux serverį", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Pašalinkite savo php.ini faile open_basedir nustatymą arba persijunkite į 64-bitų PHP.", + "Set an admin username." : "Nustatyti administratoriaus naudotojo vardą.", + "Set an admin password." : "Nustatyti administratoriaus slaptažodį.", + "Can't create or write into the data directory %s" : "Negalima nuskaityti arba rašyti į duomenų katalogą. %s", + "Invalid Federated Cloud ID" : "Neteisingas federacinės debesijos ID", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s pasidalino „%2$s“ su jumis ir parašė pastabą:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s pasidalino „%2$s“ su jumis ir parašė pastabą", + "»%s« added a note to a file shared with you" : "„%s“ parašė pastabą su jumis pasidalintam failui", + "Open »%s«" : "Atverti \"%s\"", + "%1$s via %2$s" : "%1$s per %2$s", + "You are not allowed to share %s" : "Jums neleidžiama bendrinti %s", + "Can’t increase permissions of %s" : "Negalima pridėti papildomų %s leidimų", + "Files can’t be shared with delete permissions" : "Failai negali būti bendrinami su trynimo leidimu.", + "Files can’t be shared with create permissions" : "Failai negali būti bendrinami su sukūrimo leidimu.", + "Expiration date is in the past" : "Bendrinimo pabaigos data yra praėjęs laikas", + "Can’t set expiration date more than %s days in the future" : "Negalima nustatyti galiojimo laiko ilgesnio nei %s dienos.", + "%1$s shared »%2$s« with you" : "%1$s pasidalino „%2$s“ su jumis", + "%1$s shared »%2$s« with you." : "%1$s pasidalino „%2$s“ su jumis.", + "Click the button below to open it." : "Norėdami atverti failą, spustelėkite mygtuką žemiau.", + "The requested share does not exist anymore" : "Pageidaujamas bendrinimas daugiau neegzistuoja.", + "Could not find category \"%s\"" : "Nepavyko rasti kategorijos „%s“", + "Sunday" : "Sekmadienis", + "Monday" : "Pirmadienis", + "Tuesday" : "Antradienis", + "Wednesday" : "Trečiadienis", + "Thursday" : "Ketvirtadienis", + "Friday" : "Penktadienis", + "Saturday" : "Šeštadienis", + "Sun." : "Sek.", + "Mon." : "Pir.", + "Tue." : "Ant.", + "Wed." : "Tre.", + "Thu." : "Ket.", + "Fri." : "Pen.", + "Sat." : "Šeš.", + "Su" : "Sk", + "Mo" : "Pr", + "Tu" : "An", + "We" : "Tr", + "Th" : "Kt", + "Fr" : "Pn", + "Sa" : "Št", + "January" : "Sausis", + "February" : "Vasaris", + "March" : "Kovas", + "April" : "Balandis", + "May" : "Gegužė", + "June" : "Birželis", + "July" : "Liepa", + "August" : "Rugpjūtis", + "September" : "Rugsėjis", + "October" : "Spalis", + "November" : "Lapkritis", + "December" : "Gruodis", + "Jan." : "Sau.", + "Feb." : "Vas.", + "Mar." : "Kov.", + "Apr." : "Bal.", + "May." : "Geg.", + "Jun." : "Bir.", + "Jul." : "Lie.", + "Aug." : "Rgp.", + "Sep." : "Rgs.", + "Oct." : "Spl.", + "Nov." : "Lap.", + "Dec." : "Grd.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Naudotojo varde leidžiama naudoti tik šiuos simbolius: \"a-z\", \"A-Z\", \"0-9\", ir \"_.@-'\"", + "A valid username must be provided" : "Privalo būti pateiktas tinkamas naudotojo vardas", + "Username contains whitespace at the beginning or at the end" : "Naudotojo varde pradžioje ar pabaigoje yra tarpas", + "Username must not consist of dots only" : "Naudotojo vardas negali būti sudarytas tik iš taškų.", + "A valid password must be provided" : "Slaptažodis turi būti tinkamas", + "The username is already being used" : "Naudotojo vardas jau yra naudojamas", + "Could not create user" : "Nepavyko sukurti naudotojo", + "User disabled" : "Naudotojas išjungtas", + "Login canceled by app" : "Programėlė nutraukė prisijungimo procesą", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programėlė \"%1$s\" negali būti įdiegta, nes nėra patenkinamos šios priklausomybės: %2$s", + "a safe home for all your data" : "saugūs namai visiems jūsų duomenims", + "File is currently busy, please try again later" : "Failas šiuo metu yra užimtas, prašome vėliau pabandyti dar kartą", + "Can't read file" : "Nepavyksta perskaityti failo", + "Application is not enabled" : "Programa neįjungta", + "Authentication error" : "Tapatybės nustatymo klaida", + "Token expired. Please reload page." : "Pasibaigė prieigos rakto galiojimas. Prašome įkelti puslapį iš naujo.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nėra įdiegtos duomenų bazių tvarkyklės (sqlite, mysql, or postgresql)", + "Cannot write into \"config\" directory" : "Nepavyksta rašyti į \"config\" katalogą!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Tai, dažniausiai, gali būti pataisyta, suteikiant saityno serveriui rašymo prieigą prie konfigūracijos katalogo. Žiūrėkite %s", + "Cannot write into \"apps\" directory" : "Nepavyksta įrašyti į \"apps\" katalogą", + "Cannot create \"data\" directory" : "Nepavyksta sukurti katalogo \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Tai, dažniausiai, gali būti ištaisyta, suteikiant saityno serveriui rašymo prieigą prie šakninio katalogo. Žiūrėkite %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Teisės gali būti pataisytos, suteikiant saityno serveriui rašymo prieigą prie šakninio katalogo. Žiūrėkite %s", + "Setting locale to %s failed" : "Nepavyko nustatyti vietos %s", + "Please install one of these locales on your system and restart your webserver." : "Prašome įdiekite vieną šių lokalių savo sistemoje ir perkraukite žiniatinklio serverį.", + "PHP module %s not installed." : "PHP modulis %s neįdiegtas.", + "Please ask your server administrator to install the module." : "Kreipkitės į savo sistemos administratorių, kad jis įdiegtų modulį.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP nustatymas \"%s\" nenustatytas į \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Šių nustatymų pritaikymas php.ini faile, iš naujo paleis Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload yra nustatytas į \"%s\" vietoj numatomos reikšmės \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Norėdami išspręsti šią problemą, nustatykite mbstring.func_overload į 0 savo php.ini faile", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Reikalinga ne mažesnė nei libxml2 2.7.0 versija. Šiuo metu yra instaliuota %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Atnaujinkite libxml2 versiją ir perkraukite žiniatinklio serverį, kad sutvarkytumėte šią problemą.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduliai yra įdiegti, bet jų vis tiek trūksta?", + "Please ask your server administrator to restart the web server." : "Kreipkitės į savo sistemos administratorių, kad jis perkrautų žiniatinklio serverį.", + "PostgreSQL >= 9 required" : "Reikalinga PostgreSQL >= 9", + "Please upgrade your database version" : "Atnaujinkite savo duomenų bazės versiją", + "Your data directory is readable by other users" : "Duomenų katalogą gali skaityti kiti naudotojai", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Pakeiskite leidimus į 0770, kad šis katalogas negalėtų būti išvardytas kitiems naudotojams.", + "Your data directory must be an absolute path" : "Jūsų duomenų katalogas privalo būti absoliutusis kelias", + "Check the value of \"datadirectory\" in your configuration" : "Patikrinkite \"datadirectory\" reikšmę savo konfigūracijoje.", + "Your data directory is invalid" : "Neteisingas duomenų katalogas", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Įsitikinkite, kad šakniniame duomenų kataloge yra yra \".ocdata\" failas.", + "Action \"%s\" not supported or implemented." : "Veiksmas \"%s\" nepalaikomas ar neįgyvendintas.", + "Authentication failed, wrong token or provider ID given" : "Tapatybės nustatymas nepavyko, nurodytas neteisingas prieigos raktas arba teikėjo ID", + "Could not obtain lock type %d on \"%s\"." : "Nepavyko gauti užrakto tipo %d ties \"%s\".", + "Storage unauthorized. %s" : "Saugykla nesankcionuota. %s", + "Storage incomplete configuration. %s" : "Nepilna saugyklos konfigūracija. %s", + "Storage connection error. %s" : "Saugyklos sujungimo ryšio klaida. %s", + "Storage is temporarily not available" : "Saugykla yra laikinai neprieinama", + "Storage connection timeout. %s" : "Sujungimo su saugykla laikas baigėsi. %s", + "Following databases are supported: %s" : "Yra palaikomos šios duomenų bazės: %s", + "Following platforms are supported: %s" : "Yra palaikomos šios platformos: %s", + "Overview" : "Apžvalga", + "Basic settings" : "Pagrindiniai nustatymai", + "Sharing" : "Bendrinimas", + "Security" : "Saugumas", + "Groupware" : "Grupinio darbo įranga", + "Personal info" : "Asmeninė informacija", + "Mobile & desktop" : "Mobilieji ir darbalaukiai", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Tai, dažniausiai, gali būti ištaisyta, suteikiant saityno serveriui rašymo prieigą prie programų katalogo arba uždraudžiant appstore konfigūraciniame kataloge. Žiūrėkite %s" +},"pluralForm" :"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/lv.js b/docker/overlays/nextcloud/html/lib/l10n/lv.js new file mode 100644 index 0000000..900e6d0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lv.js @@ -0,0 +1,128 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nevar rakstīt \"config\" mapē!", + "This can usually be fixed by giving the webserver write access to the config directory" : "To parasti var labot, dodot tīmekļa servera rakstīšanas piekļuvi config direktorijai", + "See %s" : "Skatīt %s", + "Sample configuration detected" : "Atrasta konfigurācijas paraugs", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Konstatēts, ka paraug konfigurācija ir nokopēta. Tas var izjaukt jūsu instalāciju un nav atbalstīts. Lūdzu, izlasiet dokumentāciju, pirms veicat izmaiņas config.php", + "%1$s and %2$s" : "%1$s un %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s un %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s un %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s un %5$s", + "PHP %s or higher is required." : "Ir nepieciešams PHP %s vai jaunāks.", + "PHP with a version lower than %s is required." : "Ir nepieciešams PHP vecāks kā %s.", + "%sbit or higher PHP required." : "Nepieciešams %sbit vai jaunāks PHP.", + "The command line tool %s could not be found" : "Komandrindas rīku %s nevarēja atrast", + "The library %s is not available." : "Bibliotēka %s nav pieejama.", + "Server version %s or higher is required." : "Ir vajadzīga servera versija %s vai jaunāka.", + "Server version %s or lower is required." : "Ir vajadzīga servera versija %s vai vecāka.", + "Authentication" : "Autentifikācija", + "Unknown filetype" : "Nezināms datnes tips", + "Invalid image" : "Nederīgs attēls", + "Avatar image is not square" : "Avatar attēls nav kvadrāts", + "today" : "šodien", + "yesterday" : "vakar", + "_%n day ago_::_%n days ago_" : ["%n dienas atpakaļ","%n dienas atpakaļ","%n dienām"], + "last month" : "pagājušajā mēnesī", + "_%n month ago_::_%n months ago_" : ["%n mēneši atpakaļ","%n mēneši atpakaļ","%n mēnešiem"], + "last year" : "gājušajā gadā", + "_%n year ago_::_%n years ago_" : ["%n gadiem","%n gadiem","%n gadiem"], + "_%n hour ago_::_%n hours ago_" : ["%n stundas atpakaļ","%n stundas atpakaļ","%n stundām"], + "_%n minute ago_::_%n minutes ago_" : ["%n minūtes atpakaļ","%n minūtes atpakaļ","%n minūtēm"], + "seconds ago" : "sekundēm", + "File name is too long" : "Datnes nosaukums ir pārāk garš", + "Empty filename is not allowed" : "Tukšs datnes nosaukums nav atļauts", + "__language_name__" : "Latviešu", + "Help" : "Palīdzība", + "Apps" : "Lietotnes", + "Settings" : "Iestatījumi", + "Log out" : "Izrakstīties", + "Users" : "Lietotāji", + "Unknown user" : "Nezināms lietotājs", + "Additional settings" : "Papildu iestatījumi", + "%s enter the database username." : "%s ievadiet datubāzes lietotājvārdu.", + "%s enter the database name." : "%s ievadiet datubāzes nosaukumu.", + "%s you may not use dots in the database name" : "%s datubāžu nosaukumos nedrīkst izmantot punktus", + "Oracle connection could not be established" : "Nevar izveidot savienojumu ar Oracle", + "Oracle username and/or password not valid" : "Nav derīga Oracle parole un/vai lietotājvārds", + "PostgreSQL username and/or password not valid" : "Nav derīga PostgreSQL parole un/vai lietotājvārds", + "Set an admin username." : "Iestatiet administratora lietotājvārdu.", + "Set an admin password." : "Iestatiet administratora paroli.", + "Invalid Federated Cloud ID" : "Nederīgs Federated Cloud ID", + "Open »%s«" : "Atvērt »%s«", + "Could not find category \"%s\"" : "Nevarēja atrast kategoriju “%s”", + "Sunday" : "Svētdiena", + "Monday" : "Pirmdiena", + "Tuesday" : "Otrdiena", + "Wednesday" : "Trešdiena", + "Thursday" : "Ceturtdiena", + "Friday" : "Piektdiena", + "Saturday" : "Sestdiena", + "Sun." : "Sv.", + "Mon." : "Pr.", + "Tue." : "Ot.", + "Wed." : "Tr.", + "Thu." : "Ce.", + "Fri." : "Pk.", + "Sat." : "Se.", + "Su" : "Sv", + "Mo" : "Pr", + "Tu" : "Ot", + "We" : "Tr", + "Th" : "Ce", + "Fr" : "Pi", + "Sa" : "Se", + "January" : "Janvāris", + "February" : "Februāris", + "March" : "Marts", + "April" : "Aprīlis", + "May" : "Maijs", + "June" : "Jūnijs", + "July" : "Jūlijs", + "August" : "Augusts", + "September" : "Septembris", + "October" : "Oktobris", + "November" : "Novembris", + "December" : "Decembris", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jūn.", + "Jul." : "Jūl.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Jānorāda derīgs lietotājvārds", + "A valid password must be provided" : "Jānorāda derīga parole", + "The username is already being used" : "Šāds lietotājvārds jau tiek izmantots", + "User disabled" : "Lietotājs deaktivizēts", + "Login canceled by app" : "Pieteikšanos atcelā lietotne", + "a safe home for all your data" : "droša vieta visiem jūsu datiem", + "File is currently busy, please try again later" : "Datne pašlaik ir aizņemta. Lūdzu, vēlāk mēģiniet vēlreiz", + "Can't read file" : "Nevar nolasīt datni", + "Application is not enabled" : "Lietotne nav iespējota", + "Authentication error" : "Autentifikācijas kļūda", + "Token expired. Please reload page." : "Pilnvarai ir beidzies termiņš. Lūdzu, pārlādējiet lapu.", + "PHP module %s not installed." : "PHP modulis %s nav instalēts.", + "Please ask your server administrator to install the module." : "Lūdzu, palūdziet savam servera administratoram, lai instalē moduli.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tas ir iespējams, kešatmiņa / paātrinātājs, piemēram, Zend OPcache vai eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modulis ir uzstādīts, taču tas joprojām ir uzskatāms kā trūkstošs, pazudis?", + "Please ask your server administrator to restart the web server." : "Lūdzu, palūdziet savam servera administratoram, restartēt Web serveri.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 nepieciešams", + "Please upgrade your database version" : "Lūdzu, atjauniniet datu bāzes versiju", + "Storage unauthorized. %s" : "Krātuve neautorizēta. %s", + "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s", + "Storage connection error. %s" : "Datu savienojuma kļūda. %s", + "Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama", + "Storage connection timeout. %s" : "Datu savienojuma taimauts. %s", + "Following databases are supported: %s" : "Tiek atbalstītas šādas datu bāzes: %s", + "Following platforms are supported: %s" : "Tiek atbalstītas šādas platformas: %s", + "Sharing" : "Koplietošana", + "Personal info" : "Personiskā informācija" +}, +"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/lv.json b/docker/overlays/nextcloud/html/lib/l10n/lv.json new file mode 100644 index 0000000..9190cdb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/lv.json @@ -0,0 +1,126 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nevar rakstīt \"config\" mapē!", + "This can usually be fixed by giving the webserver write access to the config directory" : "To parasti var labot, dodot tīmekļa servera rakstīšanas piekļuvi config direktorijai", + "See %s" : "Skatīt %s", + "Sample configuration detected" : "Atrasta konfigurācijas paraugs", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Konstatēts, ka paraug konfigurācija ir nokopēta. Tas var izjaukt jūsu instalāciju un nav atbalstīts. Lūdzu, izlasiet dokumentāciju, pirms veicat izmaiņas config.php", + "%1$s and %2$s" : "%1$s un %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s un %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s un %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s un %5$s", + "PHP %s or higher is required." : "Ir nepieciešams PHP %s vai jaunāks.", + "PHP with a version lower than %s is required." : "Ir nepieciešams PHP vecāks kā %s.", + "%sbit or higher PHP required." : "Nepieciešams %sbit vai jaunāks PHP.", + "The command line tool %s could not be found" : "Komandrindas rīku %s nevarēja atrast", + "The library %s is not available." : "Bibliotēka %s nav pieejama.", + "Server version %s or higher is required." : "Ir vajadzīga servera versija %s vai jaunāka.", + "Server version %s or lower is required." : "Ir vajadzīga servera versija %s vai vecāka.", + "Authentication" : "Autentifikācija", + "Unknown filetype" : "Nezināms datnes tips", + "Invalid image" : "Nederīgs attēls", + "Avatar image is not square" : "Avatar attēls nav kvadrāts", + "today" : "šodien", + "yesterday" : "vakar", + "_%n day ago_::_%n days ago_" : ["%n dienas atpakaļ","%n dienas atpakaļ","%n dienām"], + "last month" : "pagājušajā mēnesī", + "_%n month ago_::_%n months ago_" : ["%n mēneši atpakaļ","%n mēneši atpakaļ","%n mēnešiem"], + "last year" : "gājušajā gadā", + "_%n year ago_::_%n years ago_" : ["%n gadiem","%n gadiem","%n gadiem"], + "_%n hour ago_::_%n hours ago_" : ["%n stundas atpakaļ","%n stundas atpakaļ","%n stundām"], + "_%n minute ago_::_%n minutes ago_" : ["%n minūtes atpakaļ","%n minūtes atpakaļ","%n minūtēm"], + "seconds ago" : "sekundēm", + "File name is too long" : "Datnes nosaukums ir pārāk garš", + "Empty filename is not allowed" : "Tukšs datnes nosaukums nav atļauts", + "__language_name__" : "Latviešu", + "Help" : "Palīdzība", + "Apps" : "Lietotnes", + "Settings" : "Iestatījumi", + "Log out" : "Izrakstīties", + "Users" : "Lietotāji", + "Unknown user" : "Nezināms lietotājs", + "Additional settings" : "Papildu iestatījumi", + "%s enter the database username." : "%s ievadiet datubāzes lietotājvārdu.", + "%s enter the database name." : "%s ievadiet datubāzes nosaukumu.", + "%s you may not use dots in the database name" : "%s datubāžu nosaukumos nedrīkst izmantot punktus", + "Oracle connection could not be established" : "Nevar izveidot savienojumu ar Oracle", + "Oracle username and/or password not valid" : "Nav derīga Oracle parole un/vai lietotājvārds", + "PostgreSQL username and/or password not valid" : "Nav derīga PostgreSQL parole un/vai lietotājvārds", + "Set an admin username." : "Iestatiet administratora lietotājvārdu.", + "Set an admin password." : "Iestatiet administratora paroli.", + "Invalid Federated Cloud ID" : "Nederīgs Federated Cloud ID", + "Open »%s«" : "Atvērt »%s«", + "Could not find category \"%s\"" : "Nevarēja atrast kategoriju “%s”", + "Sunday" : "Svētdiena", + "Monday" : "Pirmdiena", + "Tuesday" : "Otrdiena", + "Wednesday" : "Trešdiena", + "Thursday" : "Ceturtdiena", + "Friday" : "Piektdiena", + "Saturday" : "Sestdiena", + "Sun." : "Sv.", + "Mon." : "Pr.", + "Tue." : "Ot.", + "Wed." : "Tr.", + "Thu." : "Ce.", + "Fri." : "Pk.", + "Sat." : "Se.", + "Su" : "Sv", + "Mo" : "Pr", + "Tu" : "Ot", + "We" : "Tr", + "Th" : "Ce", + "Fr" : "Pi", + "Sa" : "Se", + "January" : "Janvāris", + "February" : "Februāris", + "March" : "Marts", + "April" : "Aprīlis", + "May" : "Maijs", + "June" : "Jūnijs", + "July" : "Jūlijs", + "August" : "Augusts", + "September" : "Septembris", + "October" : "Oktobris", + "November" : "Novembris", + "December" : "Decembris", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jūn.", + "Jul." : "Jūl.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "A valid username must be provided" : "Jānorāda derīgs lietotājvārds", + "A valid password must be provided" : "Jānorāda derīga parole", + "The username is already being used" : "Šāds lietotājvārds jau tiek izmantots", + "User disabled" : "Lietotājs deaktivizēts", + "Login canceled by app" : "Pieteikšanos atcelā lietotne", + "a safe home for all your data" : "droša vieta visiem jūsu datiem", + "File is currently busy, please try again later" : "Datne pašlaik ir aizņemta. Lūdzu, vēlāk mēģiniet vēlreiz", + "Can't read file" : "Nevar nolasīt datni", + "Application is not enabled" : "Lietotne nav iespējota", + "Authentication error" : "Autentifikācijas kļūda", + "Token expired. Please reload page." : "Pilnvarai ir beidzies termiņš. Lūdzu, pārlādējiet lapu.", + "PHP module %s not installed." : "PHP modulis %s nav instalēts.", + "Please ask your server administrator to install the module." : "Lūdzu, palūdziet savam servera administratoram, lai instalē moduli.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Tas ir iespējams, kešatmiņa / paātrinātājs, piemēram, Zend OPcache vai eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modulis ir uzstādīts, taču tas joprojām ir uzskatāms kā trūkstošs, pazudis?", + "Please ask your server administrator to restart the web server." : "Lūdzu, palūdziet savam servera administratoram, restartēt Web serveri.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 nepieciešams", + "Please upgrade your database version" : "Lūdzu, atjauniniet datu bāzes versiju", + "Storage unauthorized. %s" : "Krātuve neautorizēta. %s", + "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s", + "Storage connection error. %s" : "Datu savienojuma kļūda. %s", + "Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama", + "Storage connection timeout. %s" : "Datu savienojuma taimauts. %s", + "Following databases are supported: %s" : "Tiek atbalstītas šādas datu bāzes: %s", + "Following platforms are supported: %s" : "Tiek atbalstītas šādas platformas: %s", + "Sharing" : "Koplietošana", + "Personal info" : "Personiskā informācija" +},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/mk.js b/docker/overlays/nextcloud/html/lib/l10n/mk.js new file mode 100644 index 0000000..da0152e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/mk.js @@ -0,0 +1,170 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Не може да зе запишува во \"config\" директориумот!", + "See %s" : "Види %s", + "Sample configuration detected" : "Детектирана е едноставна конфигурација", + "Other activities" : "Други активности", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Едукативно издание", + "Enterprise bundle" : "Професионален пакет", + "Groupware bundle" : "Пакет со групни производи", + "Social sharing bundle" : "Пакет за споделување на социјални мрежи", + "PHP %s or higher is required." : "Потребно е PHP верзија %s или повисока.", + "PHP with a version lower than %s is required." : "Потебна е PHP верзија пониска од %s.", + "The following databases are supported: %s" : "Следниве бази со податоци се поддржани: %s", + "The library %s is not available." : "Библиотеката %s не е достапна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Потребна е библиотека %1$s со повисока верзија од %2$s - достапна верзија %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Потебна е библиотека %1$s со пониска верзија од %2$s - достапна верзија %3$s.", + "The following platforms are supported: %s" : "Следниве платформи се поддржани: %s", + "Server version %s or higher is required." : "Потребна е верзија %s или поголема на серверот.", + "Server version %s or lower is required." : "Потербна е верзија %s или помала на серверот.", + "Logged in user must be an admin or sub admin" : "Најавениот корисник мора да биде администратор или заменик администратор", + "Logged in user must be an admin" : "Најавениот корисник мора да биде администратор", + "Authentication" : "Автентикација", + "Unknown filetype" : "Непознат тип на датотека", + "Invalid image" : "Невалидна фотографија", + "today" : "денес", + "tomorrow" : "утре", + "yesterday" : "вчера", + "_in %n day_::_in %n days_" : ["за 1 ден","за %n дена"], + "_%n day ago_::_%n days ago_" : ["пред 1 ден","пред %n дена"], + "next month" : "следниот месец", + "last month" : "предходниот месец", + "_in %n month_::_in %n months_" : ["за 1 месец","за %n месеца"], + "_%n month ago_::_%n months ago_" : ["пред 1 месец","пред %n месеци"], + "next year" : "следниот месец", + "last year" : "предходната година", + "_in %n year_::_in %n years_" : ["за 1 година","за %n години"], + "_%n year ago_::_%n years ago_" : ["пред 1 година","пред %n години"], + "_in %n hour_::_in %n hours_" : ["за 1 час","за %n часа"], + "_%n hour ago_::_%n hours ago_" : ["пред 1 час","пред %n часа"], + "_in %n minute_::_in %n minutes_" : ["за 1 минута","за %n минути"], + "_%n minute ago_::_%n minutes ago_" : ["пред 1 минута","пред %n минути"], + "in a few seconds" : "за неколку секунди", + "seconds ago" : "пред неколку секунди", + "Empty file" : "Празна датотека", + "File name is a reserved word" : "Името на датотеката е резервиран збор", + "File name contains at least one invalid character" : "Името на датотеката соджи невалиден карактер", + "File name is too long" : "Името на датотеката е премногу долго", + "Empty filename is not allowed" : "Датотеки без име не се дозболени", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Апликацијата \"%s\" неможе да се инсталира бидејќи не е компатибилна со верзијата на серверот.", + "__language_name__" : "Македонски", + "This is an automatically sent email, please do not reply." : "Ова е автоматски испратена порака, не одговарајте на истата.", + "Help" : "Помош", + "Apps" : "Аппликации", + "Settings" : "Параметри", + "Log out" : "Одјава", + "Users" : "Корисници", + "Unknown user" : "Непознат корисник", + "Additional settings" : "Дополнителни параметри", + "%s enter the database username and name." : "%s внесете го корисничкото име и името на базата.", + "%s enter the database username." : "%s внеси го корисничкото име за базата.", + "%s enter the database name." : "%s внеси го името на базата.", + "%s you may not use dots in the database name" : "%s не можеш да користиш точки во името на базата", + "MySQL username and/or password not valid" : "Погрешно MySQL корисничко име и/или лозинка", + "Oracle username and/or password not valid" : "Oracle корисничкото име и/или лозинката не се валидни", + "PostgreSQL username and/or password not valid" : "PostgreSQL корисничкото име и/или лозинка не се валидни", + "Set an admin username." : "Постави администраторско корисничко име", + "Set an admin password." : "Постави администраторска лозинка.", + "Can't create or write into the data directory %s" : "Неможете да креирате или да запишувате во оваа папка %s", + "Invalid Federated Cloud ID" : "Невалиден федерален ID", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s сподели »%2$s« со вас и сака да додаде:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s сподели »%2$s« со вас и сака да додаде", + "»%s« added a note to a file shared with you" : "»%s« додаде белешка до датотеката што ја сподели со вас", + "Open »%s«" : "Отвори »%s«", + "%1$s via %2$s" : "%1$s преку %2$s", + "You are not allowed to share %s" : "Не сте овластени да ја споделите %s", + "Expiration date is in the past" : "Рокот на траење е во минато време", + "%1$s shared »%2$s« with you" : "%1$s сподели »%2$s« со вас", + "%1$s shared »%2$s« with you." : "%1$s сподели »%2$s« со вас.", + "Click the button below to open it." : "Кликнете на копчето подолу за да ја отворите.", + "The requested share does not exist anymore" : "Споделувањето не постои повеќе", + "Could not find category \"%s\"" : "Не можам да најдам категорија „%s“", + "Sunday" : "Недела", + "Monday" : "Понеделник", + "Tuesday" : "Вторник", + "Wednesday" : "Среда", + "Thursday" : "Четврток", + "Friday" : "Петок", + "Saturday" : "Сабота", + "Sun." : "Нед.", + "Mon." : "Пон.", + "Tue." : "Вто.", + "Wed." : "Сре.", + "Thu." : "Чет.", + "Fri." : "Пет.", + "Sat." : "Саб.", + "Su" : "Не", + "Mo" : "По", + "Tu" : "Вт", + "We" : "Ср", + "Th" : "Че", + "Fr" : "Пе", + "Sa" : "Са", + "January" : "Јануари", + "February" : "Февруари", + "March" : "Март", + "April" : "Април", + "May" : "Мај", + "June" : "Јуни", + "July" : "Јули", + "August" : "Август", + "September" : "Септември", + "October" : "Октомври", + "November" : "Ноември", + "December" : "Декември", + "Jan." : "Јан.", + "Feb." : "Фев.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Мај.", + "Jun." : "Јун.", + "Jul." : "Јул.", + "Aug." : "Авг.", + "Sep." : "Сеп.", + "Oct." : "Окт.", + "Nov." : "Ное.", + "Dec." : "Дек.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Само следниве карактери се дозволени во корисничкото име:: \"a-z\", \"A-Z\", \"0-9\", и \"_.@-'\"", + "A valid username must be provided" : "Мора да се внесе валидно корисничко име ", + "Username contains whitespace at the beginning or at the end" : "Корисничкото име содржи празно место на почетокот или на крајот", + "A valid password must be provided" : "Мора да се обезбеди валидна лозинка", + "The username is already being used" : "Корисничкото име е веќе во употреба", + "Could not create user" : "Неможе да се креира корисник", + "User disabled" : "Оневозможен корисник", + "a safe home for all your data" : "безбеден дом за сите ваши податоци", + "File is currently busy, please try again later" : "Датотеката моментално е зафатена, обидете се повторно", + "Can't read file" : "Неможе да се прочита датотеката", + "Application is not enabled" : "Апликацијата не е овозможена", + "Authentication error" : "Грешка во автентикација", + "Token expired. Please reload page." : "Жетонот е истечен. Ве молам превчитајте ја страницата.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Нема инсталирано додаток за (sqlite, mysql, или postgresql) база на податоци.", + "Cannot write into \"config\" directory" : "Не може да зе запишува во \"config\" директориумот", + "Cannot write into \"apps\" directory" : "Не може да зе запишува во \"apps\" директориумот", + "Cannot create \"data\" directory" : "Неможе да се креира директориум \"data\"", + "Setting locale to %s failed" : "Неуспешно поставување на локацијата %s ", + "PHP module %s not installed." : "PHP модулот %s не е инсталиран.", + "Please ask your server administrator to install the module." : "Замолете го сервер администраторот да го инсталира додатокот.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP поставката \"%s\" не е поставена до \"%s\".", + "Please ask your server administrator to restart the web server." : "Замолете го сервер администраторот да го рестартира веб серверот.", + "PostgreSQL >= 9 required" : "Потребно е PostgreSQL >= 9 ", + "Please upgrade your database version" : "Ве молиме надградете ја верзијата на базата со податоци", + "Storage unauthorized. %s" : "Неавторизирано складиште. %s", + "Storage incomplete configuration. %s" : "Конфигурацијата на складиштето не е комплетна. %s", + "Storage connection error. %s" : "Грешка во конекција до складиштето. %s", + "Storage is temporarily not available" : "Складиштето моментално не е достапно", + "Following databases are supported: %s" : "Следниве бази со податоци се поддржани: %s", + "Following platforms are supported: %s" : "Следниве платформи се поддржани: %s", + "Overview" : "Преглед", + "Basic settings" : "Основни параметри", + "Sharing" : "Споделување", + "Security" : "Безбедност", + "Groupware" : "Групни производи", + "Personal info" : "Лични податоци", + "Mobile & desktop" : "Мобилен & компјутер" +}, +"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/mk.json b/docker/overlays/nextcloud/html/lib/l10n/mk.json new file mode 100644 index 0000000..3302dd2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/mk.json @@ -0,0 +1,168 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Не може да зе запишува во \"config\" директориумот!", + "See %s" : "Види %s", + "Sample configuration detected" : "Детектирана е едноставна конфигурација", + "Other activities" : "Други активности", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Едукативно издание", + "Enterprise bundle" : "Професионален пакет", + "Groupware bundle" : "Пакет со групни производи", + "Social sharing bundle" : "Пакет за споделување на социјални мрежи", + "PHP %s or higher is required." : "Потребно е PHP верзија %s или повисока.", + "PHP with a version lower than %s is required." : "Потебна е PHP верзија пониска од %s.", + "The following databases are supported: %s" : "Следниве бази со податоци се поддржани: %s", + "The library %s is not available." : "Библиотеката %s не е достапна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Потребна е библиотека %1$s со повисока верзија од %2$s - достапна верзија %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Потебна е библиотека %1$s со пониска верзија од %2$s - достапна верзија %3$s.", + "The following platforms are supported: %s" : "Следниве платформи се поддржани: %s", + "Server version %s or higher is required." : "Потребна е верзија %s или поголема на серверот.", + "Server version %s or lower is required." : "Потербна е верзија %s или помала на серверот.", + "Logged in user must be an admin or sub admin" : "Најавениот корисник мора да биде администратор или заменик администратор", + "Logged in user must be an admin" : "Најавениот корисник мора да биде администратор", + "Authentication" : "Автентикација", + "Unknown filetype" : "Непознат тип на датотека", + "Invalid image" : "Невалидна фотографија", + "today" : "денес", + "tomorrow" : "утре", + "yesterday" : "вчера", + "_in %n day_::_in %n days_" : ["за 1 ден","за %n дена"], + "_%n day ago_::_%n days ago_" : ["пред 1 ден","пред %n дена"], + "next month" : "следниот месец", + "last month" : "предходниот месец", + "_in %n month_::_in %n months_" : ["за 1 месец","за %n месеца"], + "_%n month ago_::_%n months ago_" : ["пред 1 месец","пред %n месеци"], + "next year" : "следниот месец", + "last year" : "предходната година", + "_in %n year_::_in %n years_" : ["за 1 година","за %n години"], + "_%n year ago_::_%n years ago_" : ["пред 1 година","пред %n години"], + "_in %n hour_::_in %n hours_" : ["за 1 час","за %n часа"], + "_%n hour ago_::_%n hours ago_" : ["пред 1 час","пред %n часа"], + "_in %n minute_::_in %n minutes_" : ["за 1 минута","за %n минути"], + "_%n minute ago_::_%n minutes ago_" : ["пред 1 минута","пред %n минути"], + "in a few seconds" : "за неколку секунди", + "seconds ago" : "пред неколку секунди", + "Empty file" : "Празна датотека", + "File name is a reserved word" : "Името на датотеката е резервиран збор", + "File name contains at least one invalid character" : "Името на датотеката соджи невалиден карактер", + "File name is too long" : "Името на датотеката е премногу долго", + "Empty filename is not allowed" : "Датотеки без име не се дозболени", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Апликацијата \"%s\" неможе да се инсталира бидејќи не е компатибилна со верзијата на серверот.", + "__language_name__" : "Македонски", + "This is an automatically sent email, please do not reply." : "Ова е автоматски испратена порака, не одговарајте на истата.", + "Help" : "Помош", + "Apps" : "Аппликации", + "Settings" : "Параметри", + "Log out" : "Одјава", + "Users" : "Корисници", + "Unknown user" : "Непознат корисник", + "Additional settings" : "Дополнителни параметри", + "%s enter the database username and name." : "%s внесете го корисничкото име и името на базата.", + "%s enter the database username." : "%s внеси го корисничкото име за базата.", + "%s enter the database name." : "%s внеси го името на базата.", + "%s you may not use dots in the database name" : "%s не можеш да користиш точки во името на базата", + "MySQL username and/or password not valid" : "Погрешно MySQL корисничко име и/или лозинка", + "Oracle username and/or password not valid" : "Oracle корисничкото име и/или лозинката не се валидни", + "PostgreSQL username and/or password not valid" : "PostgreSQL корисничкото име и/или лозинка не се валидни", + "Set an admin username." : "Постави администраторско корисничко име", + "Set an admin password." : "Постави администраторска лозинка.", + "Can't create or write into the data directory %s" : "Неможете да креирате или да запишувате во оваа папка %s", + "Invalid Federated Cloud ID" : "Невалиден федерален ID", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s сподели »%2$s« со вас и сака да додаде:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s сподели »%2$s« со вас и сака да додаде", + "»%s« added a note to a file shared with you" : "»%s« додаде белешка до датотеката што ја сподели со вас", + "Open »%s«" : "Отвори »%s«", + "%1$s via %2$s" : "%1$s преку %2$s", + "You are not allowed to share %s" : "Не сте овластени да ја споделите %s", + "Expiration date is in the past" : "Рокот на траење е во минато време", + "%1$s shared »%2$s« with you" : "%1$s сподели »%2$s« со вас", + "%1$s shared »%2$s« with you." : "%1$s сподели »%2$s« со вас.", + "Click the button below to open it." : "Кликнете на копчето подолу за да ја отворите.", + "The requested share does not exist anymore" : "Споделувањето не постои повеќе", + "Could not find category \"%s\"" : "Не можам да најдам категорија „%s“", + "Sunday" : "Недела", + "Monday" : "Понеделник", + "Tuesday" : "Вторник", + "Wednesday" : "Среда", + "Thursday" : "Четврток", + "Friday" : "Петок", + "Saturday" : "Сабота", + "Sun." : "Нед.", + "Mon." : "Пон.", + "Tue." : "Вто.", + "Wed." : "Сре.", + "Thu." : "Чет.", + "Fri." : "Пет.", + "Sat." : "Саб.", + "Su" : "Не", + "Mo" : "По", + "Tu" : "Вт", + "We" : "Ср", + "Th" : "Че", + "Fr" : "Пе", + "Sa" : "Са", + "January" : "Јануари", + "February" : "Февруари", + "March" : "Март", + "April" : "Април", + "May" : "Мај", + "June" : "Јуни", + "July" : "Јули", + "August" : "Август", + "September" : "Септември", + "October" : "Октомври", + "November" : "Ноември", + "December" : "Декември", + "Jan." : "Јан.", + "Feb." : "Фев.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Мај.", + "Jun." : "Јун.", + "Jul." : "Јул.", + "Aug." : "Авг.", + "Sep." : "Сеп.", + "Oct." : "Окт.", + "Nov." : "Ное.", + "Dec." : "Дек.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Само следниве карактери се дозволени во корисничкото име:: \"a-z\", \"A-Z\", \"0-9\", и \"_.@-'\"", + "A valid username must be provided" : "Мора да се внесе валидно корисничко име ", + "Username contains whitespace at the beginning or at the end" : "Корисничкото име содржи празно место на почетокот или на крајот", + "A valid password must be provided" : "Мора да се обезбеди валидна лозинка", + "The username is already being used" : "Корисничкото име е веќе во употреба", + "Could not create user" : "Неможе да се креира корисник", + "User disabled" : "Оневозможен корисник", + "a safe home for all your data" : "безбеден дом за сите ваши податоци", + "File is currently busy, please try again later" : "Датотеката моментално е зафатена, обидете се повторно", + "Can't read file" : "Неможе да се прочита датотеката", + "Application is not enabled" : "Апликацијата не е овозможена", + "Authentication error" : "Грешка во автентикација", + "Token expired. Please reload page." : "Жетонот е истечен. Ве молам превчитајте ја страницата.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Нема инсталирано додаток за (sqlite, mysql, или postgresql) база на податоци.", + "Cannot write into \"config\" directory" : "Не може да зе запишува во \"config\" директориумот", + "Cannot write into \"apps\" directory" : "Не може да зе запишува во \"apps\" директориумот", + "Cannot create \"data\" directory" : "Неможе да се креира директориум \"data\"", + "Setting locale to %s failed" : "Неуспешно поставување на локацијата %s ", + "PHP module %s not installed." : "PHP модулот %s не е инсталиран.", + "Please ask your server administrator to install the module." : "Замолете го сервер администраторот да го инсталира додатокот.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP поставката \"%s\" не е поставена до \"%s\".", + "Please ask your server administrator to restart the web server." : "Замолете го сервер администраторот да го рестартира веб серверот.", + "PostgreSQL >= 9 required" : "Потребно е PostgreSQL >= 9 ", + "Please upgrade your database version" : "Ве молиме надградете ја верзијата на базата со податоци", + "Storage unauthorized. %s" : "Неавторизирано складиште. %s", + "Storage incomplete configuration. %s" : "Конфигурацијата на складиштето не е комплетна. %s", + "Storage connection error. %s" : "Грешка во конекција до складиштето. %s", + "Storage is temporarily not available" : "Складиштето моментално не е достапно", + "Following databases are supported: %s" : "Следниве бази со податоци се поддржани: %s", + "Following platforms are supported: %s" : "Следниве платформи се поддржани: %s", + "Overview" : "Преглед", + "Basic settings" : "Основни параметри", + "Sharing" : "Споделување", + "Security" : "Безбедност", + "Groupware" : "Групни производи", + "Personal info" : "Лични податоци", + "Mobile & desktop" : "Мобилен & компјутер" +},"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/mn.js b/docker/overlays/nextcloud/html/lib/l10n/mn.js new file mode 100644 index 0000000..f8a9186 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/mn.js @@ -0,0 +1,41 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\" хавтас руу бичих боломжгүй байна!", + "Unknown filetype" : "Үл мэдэгдэх файлын төрөл", + "Invalid image" : "буруу зураг", + "Avatar image is not square" : "Хөрөг зураг дөрвөлжин биш", + "today" : "өнөөдөр", + "yesterday" : "өчигдөр", + "last month" : "сүүлийн сар", + "last year" : "сүүлийн жил", + "seconds ago" : "секундийн өмнө", + "File name is a reserved word" : "Файлын нэр нь нийцгүй үг", + "File name contains at least one invalid character" : "файлын нэр нь хамгийн багадаа нэг нь хүчингүй тэмдэгт агуулж байна", + "File name is too long" : "Файлын нэр хэтэрхий урт байна", + "Dot files are not allowed" : "Dot файлууд зөвшөөрөл байхгүй байна", + "__language_name__" : "хэлний нэр", + "Help" : "Туслах", + "Apps" : "Аппликэйшинууд", + "Settings" : "Тохиргоо", + "Log out" : "гаргах", + "Users" : "хэрэглэгч", + "Unknown user" : " хэрэглэгч", + "Open »%s«" : "»%s« нээх", + "Sunday" : "ням гариг", + "Monday" : "даваа", + "Tuesday" : "мягмар", + "Wednesday" : "лхагва", + "Thursday" : "пүрэв", + "Friday" : "баасан", + "Saturday" : "бямба", + "June" : "Зургадугаар сар", + "November" : "Арван нэгдүгээр сар", + "a safe home for all your data" : "Таны өгөгдлүүдийн аюулгүй гэр", + "Authentication error" : "Нотолгооны алдаа", + "Storage is temporarily not available" : "Хадгалах төхөөрөмж нь түр хугацаанд ашиглах боломжгүй байна", + "Sharing" : "Түгээх", + "Security" : "Хамгаалалт", + "Personal info" : "Хувийн мэдээлэл" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/mn.json b/docker/overlays/nextcloud/html/lib/l10n/mn.json new file mode 100644 index 0000000..86ea54a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/mn.json @@ -0,0 +1,39 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\" хавтас руу бичих боломжгүй байна!", + "Unknown filetype" : "Үл мэдэгдэх файлын төрөл", + "Invalid image" : "буруу зураг", + "Avatar image is not square" : "Хөрөг зураг дөрвөлжин биш", + "today" : "өнөөдөр", + "yesterday" : "өчигдөр", + "last month" : "сүүлийн сар", + "last year" : "сүүлийн жил", + "seconds ago" : "секундийн өмнө", + "File name is a reserved word" : "Файлын нэр нь нийцгүй үг", + "File name contains at least one invalid character" : "файлын нэр нь хамгийн багадаа нэг нь хүчингүй тэмдэгт агуулж байна", + "File name is too long" : "Файлын нэр хэтэрхий урт байна", + "Dot files are not allowed" : "Dot файлууд зөвшөөрөл байхгүй байна", + "__language_name__" : "хэлний нэр", + "Help" : "Туслах", + "Apps" : "Аппликэйшинууд", + "Settings" : "Тохиргоо", + "Log out" : "гаргах", + "Users" : "хэрэглэгч", + "Unknown user" : " хэрэглэгч", + "Open »%s«" : "»%s« нээх", + "Sunday" : "ням гариг", + "Monday" : "даваа", + "Tuesday" : "мягмар", + "Wednesday" : "лхагва", + "Thursday" : "пүрэв", + "Friday" : "баасан", + "Saturday" : "бямба", + "June" : "Зургадугаар сар", + "November" : "Арван нэгдүгээр сар", + "a safe home for all your data" : "Таны өгөгдлүүдийн аюулгүй гэр", + "Authentication error" : "Нотолгооны алдаа", + "Storage is temporarily not available" : "Хадгалах төхөөрөмж нь түр хугацаанд ашиглах боломжгүй байна", + "Sharing" : "Түгээх", + "Security" : "Хамгаалалт", + "Personal info" : "Хувийн мэдээлэл" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ms_MY.js b/docker/overlays/nextcloud/html/lib/l10n/ms_MY.js new file mode 100644 index 0000000..27e63a5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ms_MY.js @@ -0,0 +1,50 @@ +OC.L10N.register( + "lib", + { + "__language_name__" : "Bahasa Melayu", + "Help" : "Bantuan", + "Apps" : "Aplikasi", + "Settings" : "Tetapan", + "Log out" : "Log keluar", + "Users" : "Pengguna", + "Sunday" : "Ahad", + "Monday" : "Isnin", + "Tuesday" : "Selasa", + "Wednesday" : "Rabu", + "Thursday" : "Khamis", + "Friday" : "Jumaat", + "Saturday" : "Sabtu", + "Sun." : "Ahad", + "Mon." : "Isnin", + "Tue." : "Selasa", + "Wed." : "Rabu ", + "Thu." : "Khamis", + "Fri." : "Jumaat", + "Sat." : "Sabtu", + "January" : "Januari", + "February" : "Februari", + "March" : "Mac", + "April" : "April", + "May" : "Mei", + "June" : "Jun", + "July" : "Julai", + "August" : "Ogos", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Disember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mac.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ogos.", + "Sep." : "Sept.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dis.", + "Authentication error" : "Ralat pengesahan" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ms_MY.json b/docker/overlays/nextcloud/html/lib/l10n/ms_MY.json new file mode 100644 index 0000000..48bac0f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ms_MY.json @@ -0,0 +1,48 @@ +{ "translations": { + "__language_name__" : "Bahasa Melayu", + "Help" : "Bantuan", + "Apps" : "Aplikasi", + "Settings" : "Tetapan", + "Log out" : "Log keluar", + "Users" : "Pengguna", + "Sunday" : "Ahad", + "Monday" : "Isnin", + "Tuesday" : "Selasa", + "Wednesday" : "Rabu", + "Thursday" : "Khamis", + "Friday" : "Jumaat", + "Saturday" : "Sabtu", + "Sun." : "Ahad", + "Mon." : "Isnin", + "Tue." : "Selasa", + "Wed." : "Rabu ", + "Thu." : "Khamis", + "Fri." : "Jumaat", + "Sat." : "Sabtu", + "January" : "Januari", + "February" : "Februari", + "March" : "Mac", + "April" : "April", + "May" : "Mei", + "June" : "Jun", + "July" : "Julai", + "August" : "Ogos", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Disember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mac.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ogos.", + "Sep." : "Sept.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dis.", + "Authentication error" : "Ralat pengesahan" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/nb.js b/docker/overlays/nextcloud/html/lib/l10n/nb.js new file mode 100644 index 0000000..290364b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nb.js @@ -0,0 +1,206 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Kan ikke skrive til «config»-mappen!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til config-mappen", + "See %s" : "Se %s", + "Sample configuration detected" : "Eksempeloppsett oppdaget", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det ble oppdaget at eksempeloppsettet er blitt kopiert. Dette kan ødelegge installasjonen din og støttes ikke. Les dokumentasjonen før du gjør endringer i config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "Education Edition" : "Utdanningsversjon", + "Enterprise bundle" : "Bedrifts-pakke", + "Groupware bundle" : "Gruppevare-pakke", + "Social sharing bundle" : "Sosialdelings-pakke", + "PHP %s or higher is required." : "PHP %s eller nyere kreves.", + "PHP with a version lower than %s is required." : "PHP med en versjon lavere enn %s kreves.", + "%sbit or higher PHP required." : "%sbit eller høyere PHP kreves", + "The command line tool %s could not be found" : "Kommandolinjeverktøyet %s ble ikke funnet", + "The library %s is not available." : "Biblioteket %s er ikke tilgjengelig.", + "Server version %s or higher is required." : "Serverversjon %s eller høyere kreves.", + "Server version %s or lower is required." : "Serverversjon %s eller lavere kreves.", + "Logged in user must be an admin" : "Innlogget bruker må være administrator", + "Authentication" : "Autentisering", + "Unknown filetype" : "Ukjent filtype", + "Invalid image" : "Ugyldig bilde", + "Avatar image is not square" : "Avatarbilde er ikke firkantet", + "today" : "i dag", + "tomorrow" : "I morgen", + "yesterday" : "i går", + "_in %n day_::_in %n days_" : ["om én dag","om %n dager"], + "_%n day ago_::_%n days ago_" : ["%n dag siden","%n dager siden"], + "next month" : "neste måned", + "last month" : "forrige måned", + "_in %n month_::_in %n months_" : ["neste måned","om %n måneder"], + "_%n month ago_::_%n months ago_" : ["for %n måned siden","for %n måneder siden"], + "next year" : "neste år", + "last year" : "forrige år", + "_in %n year_::_in %n years_" : ["neste år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år siden","%n år siden"], + "_in %n hour_::_in %n hours_" : ["om %n time","om én timer"], + "_%n hour ago_::_%n hours ago_" : ["for %n time siden","for %n timer siden"], + "_in %n minute_::_in %n minutes_" : ["om ett minutt","om %n minutter"], + "_%n minute ago_::_%n minutes ago_" : ["for %n minutt siden","for %n minutter siden"], + "in a few seconds" : "om noen sekunder", + "seconds ago" : "for få sekunder siden", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul med ID: %s finnes ikke. Skru den på i app-innstillingene eller kontakt en administrator.", + "File name is a reserved word" : "Filnavnet er et reservert ord", + "File name contains at least one invalid character" : "Filnavnet inneholder minst ett ulovlig tegn", + "File name is too long" : "Filnavnet er for langt", + "Dot files are not allowed" : "Punktum-filer er ikke tillatt", + "Empty filename is not allowed" : "Tomt filnavn er ikke tillatt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan ikke installeres på grunn av at appinfo-filen ikke kan leses.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Appen \"%s\" kan ikke installeres fordi det ikke er kompatibel med denne serverversjonen.", + "__language_name__" : "Norsk bokmål", + "This is an automatically sent email, please do not reply." : "Dette er en automatisk sendt e-post, ikke svar.", + "Help" : "Hjelp", + "Apps" : "Apper", + "Settings" : "Innstillinger", + "Log out" : "Logg ut", + "Users" : "Brukere", + "Unknown user" : "Ukjent bruker", + "Additional settings" : "Flere innstillinger", + "%s enter the database username and name." : "%s legg inn database brukernavn og navn.", + "%s enter the database username." : "%s legg inn brukernavn for databasen.", + "%s enter the database name." : "%s legg inn navnet på databasen.", + "%s you may not use dots in the database name" : "%s du kan ikke bruke punktum i databasenavnet", + "You need to enter details of an existing account." : "Du må legge in detaljene til en eksisterende konto.", + "Oracle connection could not be established" : "Klarte ikke å etablere forbindelse til Oracle", + "Oracle username and/or password not valid" : "Oracle-brukernavn og/eller passord er ikke gyldig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-brukernavn og/eller passord er ikke gyldig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X støttes ikke og %s vil ikke fungere korrekt på denne plattformen. Bruk på egen risiko!", + "For the best results, please consider using a GNU/Linux server instead." : "For beste resultat, vurder å bruke en GNU/Linux-server i stedet.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det ser ut for at %s-instansen kjører i et 32-bit PHP-miljø med open_basedir satt opp i php.ini. Dette vil føre til problemer med filer over 4 GB og frarådes på det sterkeste.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjern innstillingen open_basedir i php.ini eller bytt til 64-bit PHP.", + "Set an admin username." : "Sett et admin-brukernavn.", + "Set an admin password." : "Sett et admin-passord.", + "Can't create or write into the data directory %s" : "Kan ikke opprette eller skrive i datamappen %s", + "Invalid Federated Cloud ID" : "Ugyldig ID for sammenknyttet sky", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delings-server %s må implementere grensesnittet OCP\\Share_Backend", + "Sharing backend %s not found" : "Delings-server %s ikke funnet", + "Sharing backend for %s not found" : "Delings-server for %s ikke funnet", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s delte »%2$s« med deg og vil legge til:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s delte »%2$s« med deg og vil legge til", + "»%s« added a note to a file shared with you" : "»%s« la til en melding til en fil delt med deg", + "Open »%s«" : "Åpne »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har ikke lov til å dele %s", + "Can’t increase permissions of %s" : "Kan ikke øke tillatelser for %s", + "Files can’t be shared with delete permissions" : "Filer kan ikke deles med tilgang til sletting", + "Files can’t be shared with create permissions" : "Filer kan ikke deles med tilgang til opprettelse", + "Expiration date is in the past" : "Utløpsdato er i fortid", + "Can’t set expiration date more than %s days in the future" : "Kan ikke sette utløpsdato mer enn %s dager i fremtiden", + "%1$s shared »%2$s« with you" : "%1$s delte »%2$s« med deg", + "%1$s shared »%2$s« with you." : "%1$s delte »%2$s« med deg.", + "Click the button below to open it." : "Klikk på knappen nedenfor for å åpne den.", + "The requested share does not exist anymore" : "Forespurt ressurs finnes ikke lenger", + "Could not find category \"%s\"" : "Kunne ikke finne kategori \"%s\"", + "Sunday" : "Søndag", + "Monday" : "Mandag", + "Tuesday" : "Tirsdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lørdag", + "Sun." : "Søn.", + "Mon." : "Man.", + "Tue." : "Tir.", + "Wed." : "Ons.", + "Thu." : "Tirs.", + "Fri." : "Fre.", + "Sat." : "Lør.", + "Su" : "Sø", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lø", + "January" : "Januar", + "February" : "Februar", + "March" : "Mars", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Bare disse tegnene tillates i et brukernavn: \"a-z\", \"A-Z\", \"0-9\" og \"_.@-'\"", + "A valid username must be provided" : "Oppgi et gyldig brukernavn", + "Username contains whitespace at the beginning or at the end" : "Brukernavn inneholder blanke på begynnelsen eller slutten", + "Username must not consist of dots only" : "Brukernavn kan ikke bare bestå av punktum", + "A valid password must be provided" : "Oppgi et gyldig passord", + "The username is already being used" : "Brukernavnet er allerede i bruk", + "Could not create user" : "Kunne ikke opprette bruker", + "User disabled" : "Brukeren er deaktivert", + "Login canceled by app" : "Innlogging avbrutt av app", + "a safe home for all your data" : "et sikkert hjem for alle dine data", + "File is currently busy, please try again later" : "Filen er opptatt for øyeblikket, prøv igjen senere", + "Can't read file" : "Kan ikke lese fil", + "Application is not enabled" : "Appen er ikke aktivert", + "Authentication error" : "Autentikasjonsfeil", + "Token expired. Please reload page." : "Symbol utløpt. Last inn siden på nytt.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ingen databasedrivere (sqlite, mysql, or postgresql) installert.", + "Cannot write into \"config\" directory" : "Kan ikke skrive i \"config\"-mappen", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til config-mappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan ikke skrive i \"apps\"-mappen", + "Cannot create \"data\" directory" : "Kan ikke opprette \"data\"-mappe", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til rotmappen. Se %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Tillatelser kan vanligvis ordnes ved å gi webserveren skrivetilgang til rotmappen. Se %s.", + "Setting locale to %s failed" : "Setting av nasjonale innstillinger til %s mislyktes.", + "Please install one of these locales on your system and restart your webserver." : "Installer en av disse nasjonale innstillingene på systemet ditt og start webserveren på nytt.", + "PHP module %s not installed." : "PHP-modul %s er ikke installert.", + "Please ask your server administrator to install the module." : "Be server-administratoren om å installere modulen.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-innstilling \"%s\" er ikke satt til \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ved å endre denne innstillingen i php.ini gjør at Nextcloud vil kjøre igjen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er satt til \"%s\" i stedet for den forventede verdien \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Sett mbstring.func_overload til 0 in php.ini for å fikse dette problemet", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Krever minst libxml2 2.7.0. Per nå er %s installert.", + "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Det ser ut til at at PHP er satt opp til å fjerne innebygde doc-blokker. Dette gjør at flere av kjerneapplikasjonene blir utilgjengelige.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dette forårsakes sannsynligvis av hurtiglager/akselerator, som f.eks. Zend OPcache eller eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har blitt installert, men de listes fortsatt som fraværende?", + "Please ask your server administrator to restart the web server." : "Be server-administratoren om å starte webserveren på nytt.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 kreves", + "Please upgrade your database version" : "Oppgrader databaseversjonen din", + "Your data directory is readable by other users" : "Din datamappe kan leses av andre brukere", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endre tillatelsene til 0770 slik at mappen ikke kan listes av andre brukere.", + "Your data directory must be an absolute path" : "Din datamappe må være en absolutt sti", + "Check the value of \"datadirectory\" in your configuration" : "Sjekk verdien for \"datadirectory\" i oppsettet ditt", + "Your data directory is invalid" : "Din datamappe er ugyldig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Forsikre deg om at det finnes ei fil kalt \".ocdata\" på rota av datamappa.", + "Could not obtain lock type %d on \"%s\"." : "Klarte ikke å låse med type %d på \"%s\".", + "Storage unauthorized. %s" : "Lager uautorisert: %s", + "Storage incomplete configuration. %s" : "Ikke komplett oppsett for lager. %s", + "Storage connection error. %s" : "Tilkoblingsfeil for lager. %s", + "Storage is temporarily not available" : "Lagring er midlertidig utilgjengelig", + "Storage connection timeout. %s" : "Tidsavbrudd ved tilkobling av lager: %s", + "Following databases are supported: %s" : "Følgende databaser støttes: %s", + "Following platforms are supported: %s" : "Følgende plattformer støttes: %s", + "Overview" : "Oversikt", + "Basic settings" : "Grunninnstillinger", + "Sharing" : "Deling", + "Security" : "Sikkerhet", + "Groupware" : "Gruppevare", + "Personal info" : "Personlig informasjon", + "Mobile & desktop" : "Mobil og skrivebord", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til apps-mappen eller ved å skru av app-butikken i config-filen. Se %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/nb.json b/docker/overlays/nextcloud/html/lib/l10n/nb.json new file mode 100644 index 0000000..be76d21 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nb.json @@ -0,0 +1,204 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Kan ikke skrive til «config»-mappen!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til config-mappen", + "See %s" : "Se %s", + "Sample configuration detected" : "Eksempeloppsett oppdaget", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det ble oppdaget at eksempeloppsettet er blitt kopiert. Dette kan ødelegge installasjonen din og støttes ikke. Les dokumentasjonen før du gjør endringer i config.php", + "%1$s and %2$s" : "%1$s og %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s og %5$s", + "Education Edition" : "Utdanningsversjon", + "Enterprise bundle" : "Bedrifts-pakke", + "Groupware bundle" : "Gruppevare-pakke", + "Social sharing bundle" : "Sosialdelings-pakke", + "PHP %s or higher is required." : "PHP %s eller nyere kreves.", + "PHP with a version lower than %s is required." : "PHP med en versjon lavere enn %s kreves.", + "%sbit or higher PHP required." : "%sbit eller høyere PHP kreves", + "The command line tool %s could not be found" : "Kommandolinjeverktøyet %s ble ikke funnet", + "The library %s is not available." : "Biblioteket %s er ikke tilgjengelig.", + "Server version %s or higher is required." : "Serverversjon %s eller høyere kreves.", + "Server version %s or lower is required." : "Serverversjon %s eller lavere kreves.", + "Logged in user must be an admin" : "Innlogget bruker må være administrator", + "Authentication" : "Autentisering", + "Unknown filetype" : "Ukjent filtype", + "Invalid image" : "Ugyldig bilde", + "Avatar image is not square" : "Avatarbilde er ikke firkantet", + "today" : "i dag", + "tomorrow" : "I morgen", + "yesterday" : "i går", + "_in %n day_::_in %n days_" : ["om én dag","om %n dager"], + "_%n day ago_::_%n days ago_" : ["%n dag siden","%n dager siden"], + "next month" : "neste måned", + "last month" : "forrige måned", + "_in %n month_::_in %n months_" : ["neste måned","om %n måneder"], + "_%n month ago_::_%n months ago_" : ["for %n måned siden","for %n måneder siden"], + "next year" : "neste år", + "last year" : "forrige år", + "_in %n year_::_in %n years_" : ["neste år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år siden","%n år siden"], + "_in %n hour_::_in %n hours_" : ["om %n time","om én timer"], + "_%n hour ago_::_%n hours ago_" : ["for %n time siden","for %n timer siden"], + "_in %n minute_::_in %n minutes_" : ["om ett minutt","om %n minutter"], + "_%n minute ago_::_%n minutes ago_" : ["for %n minutt siden","for %n minutter siden"], + "in a few seconds" : "om noen sekunder", + "seconds ago" : "for få sekunder siden", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul med ID: %s finnes ikke. Skru den på i app-innstillingene eller kontakt en administrator.", + "File name is a reserved word" : "Filnavnet er et reservert ord", + "File name contains at least one invalid character" : "Filnavnet inneholder minst ett ulovlig tegn", + "File name is too long" : "Filnavnet er for langt", + "Dot files are not allowed" : "Punktum-filer er ikke tillatt", + "Empty filename is not allowed" : "Tomt filnavn er ikke tillatt", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan ikke installeres på grunn av at appinfo-filen ikke kan leses.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Appen \"%s\" kan ikke installeres fordi det ikke er kompatibel med denne serverversjonen.", + "__language_name__" : "Norsk bokmål", + "This is an automatically sent email, please do not reply." : "Dette er en automatisk sendt e-post, ikke svar.", + "Help" : "Hjelp", + "Apps" : "Apper", + "Settings" : "Innstillinger", + "Log out" : "Logg ut", + "Users" : "Brukere", + "Unknown user" : "Ukjent bruker", + "Additional settings" : "Flere innstillinger", + "%s enter the database username and name." : "%s legg inn database brukernavn og navn.", + "%s enter the database username." : "%s legg inn brukernavn for databasen.", + "%s enter the database name." : "%s legg inn navnet på databasen.", + "%s you may not use dots in the database name" : "%s du kan ikke bruke punktum i databasenavnet", + "You need to enter details of an existing account." : "Du må legge in detaljene til en eksisterende konto.", + "Oracle connection could not be established" : "Klarte ikke å etablere forbindelse til Oracle", + "Oracle username and/or password not valid" : "Oracle-brukernavn og/eller passord er ikke gyldig", + "PostgreSQL username and/or password not valid" : "PostgreSQL-brukernavn og/eller passord er ikke gyldig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X støttes ikke og %s vil ikke fungere korrekt på denne plattformen. Bruk på egen risiko!", + "For the best results, please consider using a GNU/Linux server instead." : "For beste resultat, vurder å bruke en GNU/Linux-server i stedet.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det ser ut for at %s-instansen kjører i et 32-bit PHP-miljø med open_basedir satt opp i php.ini. Dette vil føre til problemer med filer over 4 GB og frarådes på det sterkeste.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjern innstillingen open_basedir i php.ini eller bytt til 64-bit PHP.", + "Set an admin username." : "Sett et admin-brukernavn.", + "Set an admin password." : "Sett et admin-passord.", + "Can't create or write into the data directory %s" : "Kan ikke opprette eller skrive i datamappen %s", + "Invalid Federated Cloud ID" : "Ugyldig ID for sammenknyttet sky", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delings-server %s må implementere grensesnittet OCP\\Share_Backend", + "Sharing backend %s not found" : "Delings-server %s ikke funnet", + "Sharing backend for %s not found" : "Delings-server for %s ikke funnet", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s delte »%2$s« med deg og vil legge til:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s delte »%2$s« med deg og vil legge til", + "»%s« added a note to a file shared with you" : "»%s« la til en melding til en fil delt med deg", + "Open »%s«" : "Åpne »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har ikke lov til å dele %s", + "Can’t increase permissions of %s" : "Kan ikke øke tillatelser for %s", + "Files can’t be shared with delete permissions" : "Filer kan ikke deles med tilgang til sletting", + "Files can’t be shared with create permissions" : "Filer kan ikke deles med tilgang til opprettelse", + "Expiration date is in the past" : "Utløpsdato er i fortid", + "Can’t set expiration date more than %s days in the future" : "Kan ikke sette utløpsdato mer enn %s dager i fremtiden", + "%1$s shared »%2$s« with you" : "%1$s delte »%2$s« med deg", + "%1$s shared »%2$s« with you." : "%1$s delte »%2$s« med deg.", + "Click the button below to open it." : "Klikk på knappen nedenfor for å åpne den.", + "The requested share does not exist anymore" : "Forespurt ressurs finnes ikke lenger", + "Could not find category \"%s\"" : "Kunne ikke finne kategori \"%s\"", + "Sunday" : "Søndag", + "Monday" : "Mandag", + "Tuesday" : "Tirsdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lørdag", + "Sun." : "Søn.", + "Mon." : "Man.", + "Tue." : "Tir.", + "Wed." : "Ons.", + "Thu." : "Tirs.", + "Fri." : "Fre.", + "Sat." : "Lør.", + "Su" : "Sø", + "Mo" : "Ma", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lø", + "January" : "Januar", + "February" : "Februar", + "March" : "Mars", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Bare disse tegnene tillates i et brukernavn: \"a-z\", \"A-Z\", \"0-9\" og \"_.@-'\"", + "A valid username must be provided" : "Oppgi et gyldig brukernavn", + "Username contains whitespace at the beginning or at the end" : "Brukernavn inneholder blanke på begynnelsen eller slutten", + "Username must not consist of dots only" : "Brukernavn kan ikke bare bestå av punktum", + "A valid password must be provided" : "Oppgi et gyldig passord", + "The username is already being used" : "Brukernavnet er allerede i bruk", + "Could not create user" : "Kunne ikke opprette bruker", + "User disabled" : "Brukeren er deaktivert", + "Login canceled by app" : "Innlogging avbrutt av app", + "a safe home for all your data" : "et sikkert hjem for alle dine data", + "File is currently busy, please try again later" : "Filen er opptatt for øyeblikket, prøv igjen senere", + "Can't read file" : "Kan ikke lese fil", + "Application is not enabled" : "Appen er ikke aktivert", + "Authentication error" : "Autentikasjonsfeil", + "Token expired. Please reload page." : "Symbol utløpt. Last inn siden på nytt.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ingen databasedrivere (sqlite, mysql, or postgresql) installert.", + "Cannot write into \"config\" directory" : "Kan ikke skrive i \"config\"-mappen", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til config-mappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan ikke skrive i \"apps\"-mappen", + "Cannot create \"data\" directory" : "Kan ikke opprette \"data\"-mappe", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til rotmappen. Se %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Tillatelser kan vanligvis ordnes ved å gi webserveren skrivetilgang til rotmappen. Se %s.", + "Setting locale to %s failed" : "Setting av nasjonale innstillinger til %s mislyktes.", + "Please install one of these locales on your system and restart your webserver." : "Installer en av disse nasjonale innstillingene på systemet ditt og start webserveren på nytt.", + "PHP module %s not installed." : "PHP-modul %s er ikke installert.", + "Please ask your server administrator to install the module." : "Be server-administratoren om å installere modulen.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-innstilling \"%s\" er ikke satt til \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ved å endre denne innstillingen i php.ini gjør at Nextcloud vil kjøre igjen.", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload er satt til \"%s\" i stedet for den forventede verdien \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Sett mbstring.func_overload til 0 in php.ini for å fikse dette problemet", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Krever minst libxml2 2.7.0. Per nå er %s installert.", + "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Det ser ut til at at PHP er satt opp til å fjerne innebygde doc-blokker. Dette gjør at flere av kjerneapplikasjonene blir utilgjengelige.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dette forårsakes sannsynligvis av hurtiglager/akselerator, som f.eks. Zend OPcache eller eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har blitt installert, men de listes fortsatt som fraværende?", + "Please ask your server administrator to restart the web server." : "Be server-administratoren om å starte webserveren på nytt.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 kreves", + "Please upgrade your database version" : "Oppgrader databaseversjonen din", + "Your data directory is readable by other users" : "Din datamappe kan leses av andre brukere", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endre tillatelsene til 0770 slik at mappen ikke kan listes av andre brukere.", + "Your data directory must be an absolute path" : "Din datamappe må være en absolutt sti", + "Check the value of \"datadirectory\" in your configuration" : "Sjekk verdien for \"datadirectory\" i oppsettet ditt", + "Your data directory is invalid" : "Din datamappe er ugyldig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Forsikre deg om at det finnes ei fil kalt \".ocdata\" på rota av datamappa.", + "Could not obtain lock type %d on \"%s\"." : "Klarte ikke å låse med type %d på \"%s\".", + "Storage unauthorized. %s" : "Lager uautorisert: %s", + "Storage incomplete configuration. %s" : "Ikke komplett oppsett for lager. %s", + "Storage connection error. %s" : "Tilkoblingsfeil for lager. %s", + "Storage is temporarily not available" : "Lagring er midlertidig utilgjengelig", + "Storage connection timeout. %s" : "Tidsavbrudd ved tilkobling av lager: %s", + "Following databases are supported: %s" : "Følgende databaser støttes: %s", + "Following platforms are supported: %s" : "Følgende plattformer støttes: %s", + "Overview" : "Oversikt", + "Basic settings" : "Grunninnstillinger", + "Sharing" : "Deling", + "Security" : "Sikkerhet", + "Groupware" : "Gruppevare", + "Personal info" : "Personlig informasjon", + "Mobile & desktop" : "Mobil og skrivebord", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dette kan vanligvis ordnes ved å gi webserveren skrivetilgang til apps-mappen eller ved å skru av app-butikken i config-filen. Se %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ne.js b/docker/overlays/nextcloud/html/lib/l10n/ne.js new file mode 100644 index 0000000..931c4f7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ne.js @@ -0,0 +1,6 @@ +OC.L10N.register( + "lib", + { + "Settings" : "सेटिङ्हरू" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ne.json b/docker/overlays/nextcloud/html/lib/l10n/ne.json new file mode 100644 index 0000000..5b6bf99 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ne.json @@ -0,0 +1,4 @@ +{ "translations": { + "Settings" : "सेटिङ्हरू" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/nl.js b/docker/overlays/nextcloud/html/lib/l10n/nl.js new file mode 100644 index 0000000..69a1bed --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nl.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Kan niet schrijven naar de \"config\" directory!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dit kan opgelost worden door de config map op de webserver schrijfrechten te geven", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Of, als je liever config.php alleen-lezen wilt houden, stel de optie \"config_is_read_only\" in op true.", + "See %s" : "Zie %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dit kan opgelost worden door de config map op de webserver schrijfrechten te geven.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Of, als je liever config.php Alleen-lezen houdt, stel de optie \"config_is_read_only\" in op true. Zie %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "De bestanden van app %1$szijn niet correct vervangen. Zorg ervoor dat de versie compatible is met de server.", + "Sample configuration detected" : "Voorbeeld configuratie gevonden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Er is gedetecteerd dat de voorbeeld configuratie is gekopieerd. Dit kan je installatie beschadigen en wordt dan ook niet ondersteund. Lees de documentatie voordat je wijzigingen aan config.php doorvoert", + "Other activities" : "Andere activiteiten", + "%1$s and %2$s" : "%1$s en %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s en %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s en %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s en %5$s", + "Education Edition" : "Onderwijs Editie", + "Enterprise bundle" : "Zakelijke bundel", + "Groupware bundle" : "Groupware bundel", + "Hub bundle" : "Hub bundel", + "Social sharing bundle" : "Sociaal delen bundel", + "PHP %s or higher is required." : "PHP %s of hoger vereist.", + "PHP with a version lower than %s is required." : "PHP met een versie lager dan %s is vereist.", + "%sbit or higher PHP required." : "%sbit of hogere PHP versie vereist.", + "The following architectures are supported: %s" : "Ondersteunde architecturen: %s", + "The following databases are supported: %s" : "De volgende databases worden ondersteund: %s", + "The command line tool %s could not be found" : "Commandoregel tool %s is niet gevonden", + "The library %s is not available." : "Library %s is niet beschikbaar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Library %1$s met een versienummer hoger dan %2$s is vereist - beschikbare versie %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Library %1$s met een versienummer lager dan %2$s is vereist - beschikbare versie %3$s.", + "The following platforms are supported: %s" : "De volgende platforms worden ondersteund: %s", + "Server version %s or higher is required." : "Serverversie %s of hoger vereist.", + "Server version %s or lower is required." : "Serverversie %s of lager vereist.", + "Logged in user must be an admin or sub admin" : "Ingelogde gebruiker moet een beheerder of subbeheerder zijn", + "Logged in user must be an admin" : "Ingelogde gebruiker moet een beheerder zijn", + "Wiping of device %s has started" : "Leegmaken van toestel %sis gestart", + "Wiping of device »%s« has started" : "Leegmaken van toestel »%s« is gestart", + "»%s« started remote wipe" : "»%s« startte leegmaken op afstand", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Toestel of applicatie »%s« heeft leegmaken op afstand gestart. Je ontvangt een ander bericht als het proces is voltooid", + "Wiping of device %s has finished" : "Leegmaken van toestel %s is voltooid", + "Wiping of device »%s« has finished" : "Leegmaken van toestel »%s« is voltooid", + "»%s« finished remote wipe" : "»%s« voltooide schoonmaken op afstand", + "Device or application »%s« has finished the remote wipe process." : "Toestel of applicatie »%s« heeft het schoonmaken op afstand voltooid.", + "Remote wipe started" : "Leegmaken op afstand gestart", + "A remote wipe was started on device %s" : "Leegmaken op afstand gestart op toestel %s", + "Remote wipe finished" : "Leegmaken op afstand beëindigd", + "The remote wipe on %s has finished" : "Leegmaken op afstand van %s is voltooid", + "Authentication" : "Authenticatie", + "Unknown filetype" : "Onbekend bestandsformaat", + "Invalid image" : "Ongeldige afbeelding", + "Avatar image is not square" : "Avatar afbeelding is niet vierkant", + "today" : "vandaag", + "tomorrow" : "morgen", + "yesterday" : "gisteren", + "_in %n day_::_in %n days_" : ["over %n dag","over %n dagen"], + "_%n day ago_::_%n days ago_" : ["%n dag geleden","%n dagen geleden"], + "next month" : "volgende maand", + "last month" : "vorige maand", + "_in %n month_::_in %n months_" : ["over %n maand","over %n maanden"], + "_%n month ago_::_%n months ago_" : ["%n maand geleden","%n maanden geleden"], + "next year" : "volgend jaar", + "last year" : "vorig jaar", + "_in %n year_::_in %n years_" : ["over %n jaar","over %n jaar"], + "_%n year ago_::_%n years ago_" : ["%n jaar geleden","%n jaren geleden"], + "_in %n hour_::_in %n hours_" : ["over %n uur","over %n uur"], + "_%n hour ago_::_%n hours ago_" : ["%n uur geleden","%n uur geleden"], + "_in %n minute_::_in %n minutes_" : ["over %n minuut","over %n minuten"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuut geleden","%n minuten geleden"], + "in a few seconds" : "over een paar seconden", + "seconds ago" : "seconden geleden", + "Empty file" : "Leeg bestand", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module met ID: %s bestaat niet. Schakel die in binnen de app-instellingen of neem contact op met je beheerder.", + "File name is a reserved word" : "Bestandsnaam is een gereserveerd woord", + "File name contains at least one invalid character" : "De bestandsnaam bevat in ieder geval één verboden teken", + "File name is too long" : "De bestandsnaam is te lang", + "Dot files are not allowed" : "Punt-bestanden zijn niet toegestaan", + "Empty filename is not allowed" : "Een lege bestandsnaam is niet toegestaan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" kan niet worden geïnstalleerd, omdat het app info bestand niet gelezen kan worden.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" kan niet worden geïnstalleerd, omdat deze niet compatible is met deze versie van de server.", + "__language_name__" : "Nederlands", + "This is an automatically sent email, please do not reply." : "Dit is een automatisch gegenereerde e-mail, dus reageren is niet mogelijk.", + "Help" : "Help", + "Apps" : "Apps", + "Settings" : "Instellingen", + "Log out" : "Uitloggen", + "Users" : "Gebruikers", + "Unknown user" : "Onbekende gebruiker", + "Additional settings" : "Aanvullende instellingen", + "%s enter the database username and name." : "%s voer de database gebruikersnaam en naam in .", + "%s enter the database username." : "%s voer de database gebruikersnaam in.", + "%s enter the database name." : "%s voer de databasenaam in.", + "%s you may not use dots in the database name" : "%s er mogen geen punten in de databasenaam voorkomen", + "MySQL username and/or password not valid" : "MySQL gebruikersnaam en/of wachtwoord ongeldig", + "You need to enter details of an existing account." : "Geef de details van een bestaand account op.", + "Oracle connection could not be established" : "Er kon geen verbinding met Oracle worden gemaakt.", + "Oracle username and/or password not valid" : "Oracle gebruikersnaam en/of wachtwoord ongeldig", + "PostgreSQL username and/or password not valid" : "PostgreSQL gebruikersnaam en/of wachtwoord ongeldig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OSX wordt niet ondersteund en %s zal niet goed werken op dit platform. Gebruik het op eigen risico!", + "For the best results, please consider using a GNU/Linux server instead." : "Voor het beste resultaat adviseren wij het gebruik van een GNU/Linux server.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Het lijkt erop dat deze %s versie draait in een 32 bits PHP omgeving en dat open_basedir is geconfigureerd in php.ini. Dat zal leiden tot problemen met bestanden groter dan 4 GB en wordt dus sterk afgeraden.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Verwijder de open_basedir instelling in php.ini of schakel over op de 64bit PHP.", + "Set an admin username." : "Stel de gebruikersnaam van de beheerder in.", + "Set an admin password." : "Stel een beheerders wachtwoord in.", + "Can't create or write into the data directory %s" : "Kan niets creëren of wegschrijven in de datadirectory %s", + "Invalid Federated Cloud ID" : "Ongeldige gefedereerde Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "De gedeelde achtergrond %s moet de OCP\\Share_Backend interface implementeren", + "Sharing backend %s not found" : "De gedeelde backend %s is niet gevonden", + "Sharing backend for %s not found" : "De gedeelde backend voor %s is niet gevonden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s deelde »%2$s« met jou en wil toevoegen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s deelde »%2$s« met jou en wil toevoegen", + "»%s« added a note to a file shared with you" : "»%s« voegde een notitie toe aan een bestand dat met jou is gedeeld", + "Open »%s«" : "Open »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Je bent niet bevoegd om %s te delen", + "Can’t increase permissions of %s" : "Kan niet meer rechten geven aan %s", + "Files can’t be shared with delete permissions" : "Bestanden kunnen niet worden gedeeld met verwijder permissies", + "Files can’t be shared with create permissions" : "Bestanden kunnen niet worden gedeeld met 'creëer' permissies", + "Expiration date is in the past" : "De vervaldatum ligt in het verleden", + "Can’t set expiration date more than %s days in the future" : "Kan de vervaldatum niet meer dan %s dagen in de toekomst instellen", + "%1$s shared »%2$s« with you" : "%1$s deelde »%2$s« met jou", + "%1$s shared »%2$s« with you." : "%1$s deelde »%2$s« met jou.", + "Click the button below to open it." : "Klik de onderstaande button om te openen.", + "The requested share does not exist anymore" : "De toegang tot de gedeelde folder bestaat niet meer", + "Could not find category \"%s\"" : "Kan categorie \"%s\" niet vinden", + "Sunday" : "Zondag", + "Monday" : "Maandag", + "Tuesday" : "Dinsdag", + "Wednesday" : "Woensdag", + "Thursday" : "Donderdag", + "Friday" : "Vrijdag", + "Saturday" : "Zaterdag", + "Sun." : "Zo.", + "Mon." : "Ma.", + "Tue." : "Di.", + "Wed." : "Wo.", + "Thu." : "Do.", + "Fri." : "Vr.", + "Sat." : "Za.", + "Su" : "Zo", + "Mo" : "Ma", + "Tu" : "Di", + "We" : "Wo", + "Th" : "Do", + "Fr" : "Vr", + "Sa" : "Za", + "January" : "Januari", + "February" : "Februari", + "March" : "Maart", + "April" : "April", + "May" : "Mei", + "June" : "Juni", + "July" : "Juli", + "August" : "Augustus", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mrt.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"", + "A valid username must be provided" : "Er moet een geldige gebruikersnaam worden opgegeven", + "Username contains whitespace at the beginning or at the end" : "De gebruikersnaam bevat spaties aan het begin of aan het eind", + "Username must not consist of dots only" : "De gebruikersnaam mag niet uit alleen punten bestaan", + "Username is invalid because files already exist for this user" : "Gebruikersnaam is ongeldig omdat er al bestanden voor deze gebruiker bestaan", + "A valid password must be provided" : "Er moet een geldig wachtwoord worden opgegeven", + "The username is already being used" : "De gebruikersnaam bestaat al", + "Could not create user" : "Kan gebruiker niet aanmaken.", + "User disabled" : "Gebruiker geblokkeerd", + "Login canceled by app" : "Inloggen geannuleerd door app", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "App \"%1$s\" kan niet worden geïnstalleerd, omdat de volgende afhankelijkheden niet zijn ingevuld: %2$s", + "a safe home for all your data" : "een veilige plek voor al je gegevens", + "File is currently busy, please try again later" : "Bestandsverwerking bezig, probeer het later opnieuw", + "Can't read file" : "Kan bestand niet lezen", + "Application is not enabled" : "De applicatie is niet ingeschakeld", + "Authentication error" : "Authenticatiefout", + "Token expired. Please reload page." : "Token verlopen. Herlaad de pagina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Geen database drivers (sqlite, mysql of postgres) geïnstalleerd.", + "Cannot write into \"config\" directory" : "Kan niet schrijven naar de \"config\" directory", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dit kan opgelost worden door de config map op de webserver schrijf rechten te geven. See %s", + "Cannot write into \"apps\" directory" : "Kan niet schrijven naar de \"apps\" directory", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dit kan hersteld worden door de webserver schrijfrechten te geven op de appsdirectory of door de appstore te deactiveren in het configuratie bestand.", + "Cannot create \"data\" directory" : "\"data\" map kan niet worden aangemaakt", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dit kan hersteld worden door de root map schrijf rechten te geven op de webserver. Zie %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rechten kunnen worden hersteld door de root map op de webserver schrijf toegang te geven. Zie %s.", + "Setting locale to %s failed" : "Instellen taal op %s mislukte", + "Please install one of these locales on your system and restart your webserver." : "Installeer één van de talen op je systeem en herstart je webserver.", + "PHP module %s not installed." : "PHP module %s niet geïnstalleerd.", + "Please ask your server administrator to install the module." : "Vraag je beheerder om de module te installeren.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP instelling \"%s\" staat niet op \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Het aanpassen van deze instelling in php.ini zorgt ervoor dat Nextcloud weer start", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload is ingesteld op \"%s\" in plaats van de verwachte waarde \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Om dit probleem op te lossen stel je in php.ini mbstring.func_overload in op 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "De minimale versie van libxml2 versie is 2.7.0. Momenteel is versie%s geïnstalleerd.", + "To fix this issue update your libxml2 version and restart your web server." : "Om dit probleem op te lossen, moet je de libxml2 versie bijwerken en je webserver herstarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is nu zo ingesteld dat 'inline doc blocks' worden gestript. Hierdoor worden verschillende hoofd modules onbruikbaar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dit wordt vermoedelijk veroorzaakt door een cache/accelerator, zoals Zend OPcache of eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modules zijn geïnstalleerd, maar ze worden nog steeds als ontbrekend aangegeven?", + "Please ask your server administrator to restart the web server." : "Vraag je beheerder de webserver opnieuw te starten.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 is vereist", + "Please upgrade your database version" : "Werk je databaseversie bij", + "Your data directory is readable by other users" : "Je datamap is leesbaar voor andere gebruikers", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Wijzig de permissie in 0770 zodat de directory niet door andere gebruikers bekeken kan worden.", + "Your data directory must be an absolute path" : "Je datamap moet een absolute bestandslocatie hebben", + "Check the value of \"datadirectory\" in your configuration" : "Controleer de waarde van \"datadirectory\" in je configuratie", + "Your data directory is invalid" : "Je datamap is ongeldig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Zorg dat er een bestand genaamd \".ocdata\" in de hoofddirectory aanwezig is.", + "Action \"%s\" not supported or implemented." : "Actie \"%s\" niet ondersteund of geïmplementeerd.", + "Authentication failed, wrong token or provider ID given" : "Authenticatie mislukt, ongeldig token of provider ID opgegeven", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Er ontbreken parameters om de aanvraag uit te voeren. Ontbrekende parameters: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wordt al gebruikt door cloud federatieprovider \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud Federationprovider met ID: \"%s\" bestaat niet.", + "Could not obtain lock type %d on \"%s\"." : "Kon geen lock type %d krijgen op \"%s\".", + "Storage unauthorized. %s" : "Opslag niet toegestaan. %s", + "Storage incomplete configuration. %s" : "Incomplete opslag configuratie. %s", + "Storage connection error. %s" : "Opslag verbindingsfout. %s", + "Storage is temporarily not available" : "Opslag is tijdelijk niet beschikbaar", + "Storage connection timeout. %s" : "Opslag verbinding time-out. %s", + "Following databases are supported: %s" : "De volgende databases worden ondersteund: %s", + "Following platforms are supported: %s" : "De volgende platformen worden ondersteund: %s", + "Overview" : "Overzicht", + "Basic settings" : "Basis-instellingen", + "Sharing" : "Delen", + "Security" : "Beveiliging", + "Groupware" : "Groupware", + "Personal info" : "Persoonlijke informatie", + "Mobile & desktop" : "Mobiel & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dit kan hersteld worden door de app map schrijf rechten te geven iin de webserver of schakel de appstore uit bij het config bestand. Zie %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/nl.json b/docker/overlays/nextcloud/html/lib/l10n/nl.json new file mode 100644 index 0000000..3e068ad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nl.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Kan niet schrijven naar de \"config\" directory!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Dit kan opgelost worden door de config map op de webserver schrijfrechten te geven", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Of, als je liever config.php alleen-lezen wilt houden, stel de optie \"config_is_read_only\" in op true.", + "See %s" : "Zie %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Dit kan opgelost worden door de config map op de webserver schrijfrechten te geven.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Of, als je liever config.php Alleen-lezen houdt, stel de optie \"config_is_read_only\" in op true. Zie %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "De bestanden van app %1$szijn niet correct vervangen. Zorg ervoor dat de versie compatible is met de server.", + "Sample configuration detected" : "Voorbeeld configuratie gevonden", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Er is gedetecteerd dat de voorbeeld configuratie is gekopieerd. Dit kan je installatie beschadigen en wordt dan ook niet ondersteund. Lees de documentatie voordat je wijzigingen aan config.php doorvoert", + "Other activities" : "Andere activiteiten", + "%1$s and %2$s" : "%1$s en %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s en %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s en %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s en %5$s", + "Education Edition" : "Onderwijs Editie", + "Enterprise bundle" : "Zakelijke bundel", + "Groupware bundle" : "Groupware bundel", + "Hub bundle" : "Hub bundel", + "Social sharing bundle" : "Sociaal delen bundel", + "PHP %s or higher is required." : "PHP %s of hoger vereist.", + "PHP with a version lower than %s is required." : "PHP met een versie lager dan %s is vereist.", + "%sbit or higher PHP required." : "%sbit of hogere PHP versie vereist.", + "The following architectures are supported: %s" : "Ondersteunde architecturen: %s", + "The following databases are supported: %s" : "De volgende databases worden ondersteund: %s", + "The command line tool %s could not be found" : "Commandoregel tool %s is niet gevonden", + "The library %s is not available." : "Library %s is niet beschikbaar.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Library %1$s met een versienummer hoger dan %2$s is vereist - beschikbare versie %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Library %1$s met een versienummer lager dan %2$s is vereist - beschikbare versie %3$s.", + "The following platforms are supported: %s" : "De volgende platforms worden ondersteund: %s", + "Server version %s or higher is required." : "Serverversie %s of hoger vereist.", + "Server version %s or lower is required." : "Serverversie %s of lager vereist.", + "Logged in user must be an admin or sub admin" : "Ingelogde gebruiker moet een beheerder of subbeheerder zijn", + "Logged in user must be an admin" : "Ingelogde gebruiker moet een beheerder zijn", + "Wiping of device %s has started" : "Leegmaken van toestel %sis gestart", + "Wiping of device »%s« has started" : "Leegmaken van toestel »%s« is gestart", + "»%s« started remote wipe" : "»%s« startte leegmaken op afstand", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Toestel of applicatie »%s« heeft leegmaken op afstand gestart. Je ontvangt een ander bericht als het proces is voltooid", + "Wiping of device %s has finished" : "Leegmaken van toestel %s is voltooid", + "Wiping of device »%s« has finished" : "Leegmaken van toestel »%s« is voltooid", + "»%s« finished remote wipe" : "»%s« voltooide schoonmaken op afstand", + "Device or application »%s« has finished the remote wipe process." : "Toestel of applicatie »%s« heeft het schoonmaken op afstand voltooid.", + "Remote wipe started" : "Leegmaken op afstand gestart", + "A remote wipe was started on device %s" : "Leegmaken op afstand gestart op toestel %s", + "Remote wipe finished" : "Leegmaken op afstand beëindigd", + "The remote wipe on %s has finished" : "Leegmaken op afstand van %s is voltooid", + "Authentication" : "Authenticatie", + "Unknown filetype" : "Onbekend bestandsformaat", + "Invalid image" : "Ongeldige afbeelding", + "Avatar image is not square" : "Avatar afbeelding is niet vierkant", + "today" : "vandaag", + "tomorrow" : "morgen", + "yesterday" : "gisteren", + "_in %n day_::_in %n days_" : ["over %n dag","over %n dagen"], + "_%n day ago_::_%n days ago_" : ["%n dag geleden","%n dagen geleden"], + "next month" : "volgende maand", + "last month" : "vorige maand", + "_in %n month_::_in %n months_" : ["over %n maand","over %n maanden"], + "_%n month ago_::_%n months ago_" : ["%n maand geleden","%n maanden geleden"], + "next year" : "volgend jaar", + "last year" : "vorig jaar", + "_in %n year_::_in %n years_" : ["over %n jaar","over %n jaar"], + "_%n year ago_::_%n years ago_" : ["%n jaar geleden","%n jaren geleden"], + "_in %n hour_::_in %n hours_" : ["over %n uur","over %n uur"], + "_%n hour ago_::_%n hours ago_" : ["%n uur geleden","%n uur geleden"], + "_in %n minute_::_in %n minutes_" : ["over %n minuut","over %n minuten"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuut geleden","%n minuten geleden"], + "in a few seconds" : "over een paar seconden", + "seconds ago" : "seconden geleden", + "Empty file" : "Leeg bestand", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module met ID: %s bestaat niet. Schakel die in binnen de app-instellingen of neem contact op met je beheerder.", + "File name is a reserved word" : "Bestandsnaam is een gereserveerd woord", + "File name contains at least one invalid character" : "De bestandsnaam bevat in ieder geval één verboden teken", + "File name is too long" : "De bestandsnaam is te lang", + "Dot files are not allowed" : "Punt-bestanden zijn niet toegestaan", + "Empty filename is not allowed" : "Een lege bestandsnaam is niet toegestaan", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" kan niet worden geïnstalleerd, omdat het app info bestand niet gelezen kan worden.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" kan niet worden geïnstalleerd, omdat deze niet compatible is met deze versie van de server.", + "__language_name__" : "Nederlands", + "This is an automatically sent email, please do not reply." : "Dit is een automatisch gegenereerde e-mail, dus reageren is niet mogelijk.", + "Help" : "Help", + "Apps" : "Apps", + "Settings" : "Instellingen", + "Log out" : "Uitloggen", + "Users" : "Gebruikers", + "Unknown user" : "Onbekende gebruiker", + "Additional settings" : "Aanvullende instellingen", + "%s enter the database username and name." : "%s voer de database gebruikersnaam en naam in .", + "%s enter the database username." : "%s voer de database gebruikersnaam in.", + "%s enter the database name." : "%s voer de databasenaam in.", + "%s you may not use dots in the database name" : "%s er mogen geen punten in de databasenaam voorkomen", + "MySQL username and/or password not valid" : "MySQL gebruikersnaam en/of wachtwoord ongeldig", + "You need to enter details of an existing account." : "Geef de details van een bestaand account op.", + "Oracle connection could not be established" : "Er kon geen verbinding met Oracle worden gemaakt.", + "Oracle username and/or password not valid" : "Oracle gebruikersnaam en/of wachtwoord ongeldig", + "PostgreSQL username and/or password not valid" : "PostgreSQL gebruikersnaam en/of wachtwoord ongeldig", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OSX wordt niet ondersteund en %s zal niet goed werken op dit platform. Gebruik het op eigen risico!", + "For the best results, please consider using a GNU/Linux server instead." : "Voor het beste resultaat adviseren wij het gebruik van een GNU/Linux server.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Het lijkt erop dat deze %s versie draait in een 32 bits PHP omgeving en dat open_basedir is geconfigureerd in php.ini. Dat zal leiden tot problemen met bestanden groter dan 4 GB en wordt dus sterk afgeraden.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Verwijder de open_basedir instelling in php.ini of schakel over op de 64bit PHP.", + "Set an admin username." : "Stel de gebruikersnaam van de beheerder in.", + "Set an admin password." : "Stel een beheerders wachtwoord in.", + "Can't create or write into the data directory %s" : "Kan niets creëren of wegschrijven in de datadirectory %s", + "Invalid Federated Cloud ID" : "Ongeldige gefedereerde Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "De gedeelde achtergrond %s moet de OCP\\Share_Backend interface implementeren", + "Sharing backend %s not found" : "De gedeelde backend %s is niet gevonden", + "Sharing backend for %s not found" : "De gedeelde backend voor %s is niet gevonden", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s deelde »%2$s« met jou en wil toevoegen:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s deelde »%2$s« met jou en wil toevoegen", + "»%s« added a note to a file shared with you" : "»%s« voegde een notitie toe aan een bestand dat met jou is gedeeld", + "Open »%s«" : "Open »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Je bent niet bevoegd om %s te delen", + "Can’t increase permissions of %s" : "Kan niet meer rechten geven aan %s", + "Files can’t be shared with delete permissions" : "Bestanden kunnen niet worden gedeeld met verwijder permissies", + "Files can’t be shared with create permissions" : "Bestanden kunnen niet worden gedeeld met 'creëer' permissies", + "Expiration date is in the past" : "De vervaldatum ligt in het verleden", + "Can’t set expiration date more than %s days in the future" : "Kan de vervaldatum niet meer dan %s dagen in de toekomst instellen", + "%1$s shared »%2$s« with you" : "%1$s deelde »%2$s« met jou", + "%1$s shared »%2$s« with you." : "%1$s deelde »%2$s« met jou.", + "Click the button below to open it." : "Klik de onderstaande button om te openen.", + "The requested share does not exist anymore" : "De toegang tot de gedeelde folder bestaat niet meer", + "Could not find category \"%s\"" : "Kan categorie \"%s\" niet vinden", + "Sunday" : "Zondag", + "Monday" : "Maandag", + "Tuesday" : "Dinsdag", + "Wednesday" : "Woensdag", + "Thursday" : "Donderdag", + "Friday" : "Vrijdag", + "Saturday" : "Zaterdag", + "Sun." : "Zo.", + "Mon." : "Ma.", + "Tue." : "Di.", + "Wed." : "Wo.", + "Thu." : "Do.", + "Fri." : "Vr.", + "Sat." : "Za.", + "Su" : "Zo", + "Mo" : "Ma", + "Tu" : "Di", + "We" : "Wo", + "Th" : "Do", + "Fr" : "Vr", + "Sa" : "Za", + "January" : "Januari", + "February" : "Februari", + "March" : "Maart", + "April" : "April", + "May" : "Mei", + "June" : "Juni", + "July" : "Juli", + "August" : "Augustus", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mrt.", + "Apr." : "Apr.", + "May." : "Mei", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"", + "A valid username must be provided" : "Er moet een geldige gebruikersnaam worden opgegeven", + "Username contains whitespace at the beginning or at the end" : "De gebruikersnaam bevat spaties aan het begin of aan het eind", + "Username must not consist of dots only" : "De gebruikersnaam mag niet uit alleen punten bestaan", + "Username is invalid because files already exist for this user" : "Gebruikersnaam is ongeldig omdat er al bestanden voor deze gebruiker bestaan", + "A valid password must be provided" : "Er moet een geldig wachtwoord worden opgegeven", + "The username is already being used" : "De gebruikersnaam bestaat al", + "Could not create user" : "Kan gebruiker niet aanmaken.", + "User disabled" : "Gebruiker geblokkeerd", + "Login canceled by app" : "Inloggen geannuleerd door app", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "App \"%1$s\" kan niet worden geïnstalleerd, omdat de volgende afhankelijkheden niet zijn ingevuld: %2$s", + "a safe home for all your data" : "een veilige plek voor al je gegevens", + "File is currently busy, please try again later" : "Bestandsverwerking bezig, probeer het later opnieuw", + "Can't read file" : "Kan bestand niet lezen", + "Application is not enabled" : "De applicatie is niet ingeschakeld", + "Authentication error" : "Authenticatiefout", + "Token expired. Please reload page." : "Token verlopen. Herlaad de pagina.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Geen database drivers (sqlite, mysql of postgres) geïnstalleerd.", + "Cannot write into \"config\" directory" : "Kan niet schrijven naar de \"config\" directory", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Dit kan opgelost worden door de config map op de webserver schrijf rechten te geven. See %s", + "Cannot write into \"apps\" directory" : "Kan niet schrijven naar de \"apps\" directory", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Dit kan hersteld worden door de webserver schrijfrechten te geven op de appsdirectory of door de appstore te deactiveren in het configuratie bestand.", + "Cannot create \"data\" directory" : "\"data\" map kan niet worden aangemaakt", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Dit kan hersteld worden door de root map schrijf rechten te geven op de webserver. Zie %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rechten kunnen worden hersteld door de root map op de webserver schrijf toegang te geven. Zie %s.", + "Setting locale to %s failed" : "Instellen taal op %s mislukte", + "Please install one of these locales on your system and restart your webserver." : "Installeer één van de talen op je systeem en herstart je webserver.", + "PHP module %s not installed." : "PHP module %s niet geïnstalleerd.", + "Please ask your server administrator to install the module." : "Vraag je beheerder om de module te installeren.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP instelling \"%s\" staat niet op \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Het aanpassen van deze instelling in php.ini zorgt ervoor dat Nextcloud weer start", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload is ingesteld op \"%s\" in plaats van de verwachte waarde \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Om dit probleem op te lossen stel je in php.ini mbstring.func_overload in op 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "De minimale versie van libxml2 versie is 2.7.0. Momenteel is versie%s geïnstalleerd.", + "To fix this issue update your libxml2 version and restart your web server." : "Om dit probleem op te lossen, moet je de libxml2 versie bijwerken en je webserver herstarten.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is nu zo ingesteld dat 'inline doc blocks' worden gestript. Hierdoor worden verschillende hoofd modules onbruikbaar.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dit wordt vermoedelijk veroorzaakt door een cache/accelerator, zoals Zend OPcache of eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modules zijn geïnstalleerd, maar ze worden nog steeds als ontbrekend aangegeven?", + "Please ask your server administrator to restart the web server." : "Vraag je beheerder de webserver opnieuw te starten.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 is vereist", + "Please upgrade your database version" : "Werk je databaseversie bij", + "Your data directory is readable by other users" : "Je datamap is leesbaar voor andere gebruikers", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Wijzig de permissie in 0770 zodat de directory niet door andere gebruikers bekeken kan worden.", + "Your data directory must be an absolute path" : "Je datamap moet een absolute bestandslocatie hebben", + "Check the value of \"datadirectory\" in your configuration" : "Controleer de waarde van \"datadirectory\" in je configuratie", + "Your data directory is invalid" : "Je datamap is ongeldig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Zorg dat er een bestand genaamd \".ocdata\" in de hoofddirectory aanwezig is.", + "Action \"%s\" not supported or implemented." : "Actie \"%s\" niet ondersteund of geïmplementeerd.", + "Authentication failed, wrong token or provider ID given" : "Authenticatie mislukt, ongeldig token of provider ID opgegeven", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Er ontbreken parameters om de aanvraag uit te voeren. Ontbrekende parameters: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" wordt al gebruikt door cloud federatieprovider \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud Federationprovider met ID: \"%s\" bestaat niet.", + "Could not obtain lock type %d on \"%s\"." : "Kon geen lock type %d krijgen op \"%s\".", + "Storage unauthorized. %s" : "Opslag niet toegestaan. %s", + "Storage incomplete configuration. %s" : "Incomplete opslag configuratie. %s", + "Storage connection error. %s" : "Opslag verbindingsfout. %s", + "Storage is temporarily not available" : "Opslag is tijdelijk niet beschikbaar", + "Storage connection timeout. %s" : "Opslag verbinding time-out. %s", + "Following databases are supported: %s" : "De volgende databases worden ondersteund: %s", + "Following platforms are supported: %s" : "De volgende platformen worden ondersteund: %s", + "Overview" : "Overzicht", + "Basic settings" : "Basis-instellingen", + "Sharing" : "Delen", + "Security" : "Beveiliging", + "Groupware" : "Groupware", + "Personal info" : "Persoonlijke informatie", + "Mobile & desktop" : "Mobiel & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Dit kan hersteld worden door de app map schrijf rechten te geven iin de webserver of schakel de appstore uit bij het config bestand. Zie %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/nn_NO.js b/docker/overlays/nextcloud/html/lib/l10n/nn_NO.js new file mode 100644 index 0000000..874d98f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nn_NO.js @@ -0,0 +1,68 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "Ukjend filtype", + "Invalid image" : "Ugyldig bilete", + "today" : "i dag", + "yesterday" : "i går", + "last month" : "førre månad", + "last year" : "i fjor", + "seconds ago" : "sekund sidan", + "__language_name__" : "Nynorsk", + "Help" : "Hjelp", + "Apps" : "Program", + "Settings" : "Instillingar", + "Log out" : "Logg ut", + "Users" : "Brukarar", + "Open »%s«" : "Opna »%s«", + "Sunday" : "Søndag", + "Monday" : "Måndag", + "Tuesday" : "Tysdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Laurdag", + "Sun." : "Søn.", + "Mon." : "Mån.", + "Tue." : "Tys.", + "Wed." : "Ons.", + "Thu." : "Tor.", + "Fri." : "Fre.", + "Sat." : "Lau.", + "Su" : "Su", + "Mo" : "Må", + "Tu" : "Ty", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "La", + "January" : "Januar", + "February" : "Februar", + "March" : "Mars", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar,", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "A valid username must be provided" : "Du må oppgje eit gyldig brukarnamn", + "A valid password must be provided" : "Du må oppgje eit gyldig passord", + "Authentication error" : "Feil i autentisering", + "Sharing" : "Deling" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/nn_NO.json b/docker/overlays/nextcloud/html/lib/l10n/nn_NO.json new file mode 100644 index 0000000..64d9ea2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/nn_NO.json @@ -0,0 +1,66 @@ +{ "translations": { + "Unknown filetype" : "Ukjend filtype", + "Invalid image" : "Ugyldig bilete", + "today" : "i dag", + "yesterday" : "i går", + "last month" : "førre månad", + "last year" : "i fjor", + "seconds ago" : "sekund sidan", + "__language_name__" : "Nynorsk", + "Help" : "Hjelp", + "Apps" : "Program", + "Settings" : "Instillingar", + "Log out" : "Logg ut", + "Users" : "Brukarar", + "Open »%s«" : "Opna »%s«", + "Sunday" : "Søndag", + "Monday" : "Måndag", + "Tuesday" : "Tysdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Laurdag", + "Sun." : "Søn.", + "Mon." : "Mån.", + "Tue." : "Tys.", + "Wed." : "Ons.", + "Thu." : "Tor.", + "Fri." : "Fre.", + "Sat." : "Lau.", + "Su" : "Su", + "Mo" : "Må", + "Tu" : "Ty", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "La", + "January" : "Januar", + "February" : "Februar", + "March" : "Mars", + "April" : "April", + "May" : "Mai", + "June" : "Juni", + "July" : "Juli", + "August" : "August", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "Desember", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar,", + "Apr." : "Apr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Des.", + "A valid username must be provided" : "Du må oppgje eit gyldig brukarnamn", + "A valid password must be provided" : "Du må oppgje eit gyldig passord", + "Authentication error" : "Feil i autentisering", + "Sharing" : "Deling" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/oc.js b/docker/overlays/nextcloud/html/lib/l10n/oc.js new file mode 100644 index 0000000..4ff2d89 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/oc.js @@ -0,0 +1,16 @@ +OC.L10N.register( + "lib", + { + "seconds ago" : "i a qualques segondas", + "Help" : "Ajuda", + "Settings" : "Paramètres", + "Sunday" : "Dimenge", + "Monday" : "Diluns", + "Tuesday" : "Dimars", + "Wednesday" : "Dimècres", + "Thursday" : "Dijòus", + "Friday" : "Divendres", + "Saturday" : "Dissabte", + "Sharing" : "Partiment" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/oc.json b/docker/overlays/nextcloud/html/lib/l10n/oc.json new file mode 100644 index 0000000..a05e867 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/oc.json @@ -0,0 +1,14 @@ +{ "translations": { + "seconds ago" : "i a qualques segondas", + "Help" : "Ajuda", + "Settings" : "Paramètres", + "Sunday" : "Dimenge", + "Monday" : "Diluns", + "Tuesday" : "Dimars", + "Wednesday" : "Dimècres", + "Thursday" : "Dijòus", + "Friday" : "Divendres", + "Saturday" : "Dissabte", + "Sharing" : "Partiment" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/pl.js b/docker/overlays/nextcloud/html/lib/l10n/pl.js new file mode 100644 index 0000000..125eb69 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pl.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nie można zapisać do katalogu \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Jeśli, albo wolisz zachować plik config.php tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true.", + "See %s" : "Zobacz %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Jeśli, albo wolisz zachować plik config.php tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true. Zobacz %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.", + "Sample configuration detected" : "Wykryto przykładową konfigurację", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php", + "Other activities" : "Inne czynności", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Edycja edukacyjna", + "Enterprise bundle" : "Pakiet dla firm", + "Groupware bundle" : "Pakiet do pracy grupowej", + "Hub bundle" : "Pakiet centralny", + "Social sharing bundle" : "Pakiet udostępniania społecznościowego", + "PHP %s or higher is required." : "Wymagane jest PHP %s lub wyższe.", + "PHP with a version lower than %s is required." : "Wymagane jest PHP z wersją niższą niż %s.", + "%sbit or higher PHP required." : "%sbit lub wymagane wyższe PHP.", + "The following architectures are supported: %s" : "Obsługiwane są następujące architektury: %s", + "The following databases are supported: %s" : "Obsługiwane są następujące bazy danych: %s", + "The command line tool %s could not be found" : "Nie można znaleźć narzędzia wiersza poleceń %s", + "The library %s is not available." : "Biblioteka %s nie jest dostępna.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Wymagana jest biblioteka %1$s z wersją wyższą niż %2$s - dostępna wersja %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Wymagana jest biblioteka %1$s w wersji niższej niż %2$s - dostępna wersja %3$s.", + "The following platforms are supported: %s" : "Obsługiwane są następujące platformy: %s", + "Server version %s or higher is required." : "Wymagana jest wersja serwera %s lub wyższa.", + "Server version %s or lower is required." : "Wymagana jest wersja serwera %s lub niższa.", + "Logged in user must be an admin or sub admin" : "Zalogowany użytkownik musi być administratorem lub współadministratorem", + "Logged in user must be an admin" : "Zalogowany użytkownik musi być administratorem", + "Wiping of device %s has started" : "Rozpoczęto czyszczenie urządzenia %s", + "Wiping of device »%s« has started" : "Rozpoczęto czyszczenie urządzenia »%s«", + "»%s« started remote wipe" : "»%s« rozpoczęło zdalne czyszczenie", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Urządzenie lub aplikacja »%s« uruchomiło proces zdalnego czyszczenia. Otrzymasz kolejny e-mail po zakończeniu procesu", + "Wiping of device %s has finished" : "Zakończono czyszczenie urządzenia %s", + "Wiping of device »%s« has finished" : "Zakończono czyszczenie urządzenia »%s«", + "»%s« finished remote wipe" : "»%s« zakończył zdalne czyszczenie", + "Device or application »%s« has finished the remote wipe process." : "Urządzenie lub aplikacja »%s« zakończyło proces zdalnego czyszczenia.", + "Remote wipe started" : "Rozpoczęto zdalne czyszczenie", + "A remote wipe was started on device %s" : "Zdalne czyszczenie zostało uruchomione na urządzeniu %s", + "Remote wipe finished" : "Zdalne czyszczenie zakończone", + "The remote wipe on %s has finished" : "Zakończono zdalne czyszczenie na %s", + "Authentication" : "Uwierzytelnienie", + "Unknown filetype" : "Nieznany typ pliku", + "Invalid image" : "Nieprawidłowy obraz", + "Avatar image is not square" : "Obraz awatara nie jest kwadratowy", + "today" : "dzisiaj", + "tomorrow" : "jutro", + "yesterday" : "wczoraj", + "_in %n day_::_in %n days_" : ["za %n dzień","za %n dni","za %n dni","za %n dni"], + "_%n day ago_::_%n days ago_" : ["%d dzień temu","%n dni temu","%n dni temu","%n dni temu"], + "next month" : "następny miesiąc", + "last month" : "w zeszłym miesiącu", + "_in %n month_::_in %n months_" : ["za %n miesiąc","za %n miesiące","za %n miesięcy","za %n miesięcy"], + "_%n month ago_::_%n months ago_" : ["%n miesiąc temu","%n miesiące temu","%n miesięcy temu","%n miesięcy temu"], + "next year" : "następny rok", + "last year" : "w zeszłym roku", + "_in %n year_::_in %n years_" : ["za %n rok","za %n lata","za %n lat","za %n lat"], + "_%n year ago_::_%n years ago_" : ["%n rok temu","%n lata temu","%n lat temu","%n lat temu"], + "_in %n hour_::_in %n hours_" : ["za %n godzinę","za %n godziny","za %n godzin","za %n godziny"], + "_%n hour ago_::_%n hours ago_" : ["%n godzinę temu","%n godziny temu","%n godzin temu","%n godzin temu"], + "_in %n minute_::_in %n minutes_" : ["za %n minutę","za %n minuty","za %n minut","za %n minuty"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuta temu","%n minuty temu","%n minut temu","%n minut temu"], + "in a few seconds" : "za kilka sekund", + "seconds ago" : "przed chwilą", + "Empty file" : "Pusty plik", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Moduł o ID: %s nie istnieje. Włącz go w ustawieniach aplikacji lub skontaktuj się z administratorem.", + "File name is a reserved word" : "Nazwa pliku jest zarezerwowana", + "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", + "File name is too long" : "Nazwa pliku jest za długa", + "Dot files are not allowed" : "Pliki z kropką są nie dozwolone", + "Empty filename is not allowed" : "Pusta nazwa pliku jest niedozwolona", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikacji \"%s\" nie można zainstalować, ponieważ nie można odczytać pliku appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikacji \"%s\" nie można zainstalować, ponieważ nie jest kompatybilna z tą wersją serwera.", + "__language_name__" : "Polski", + "This is an automatically sent email, please do not reply." : "To jest automatycznie wysłany e-mail, proszę nie odpowiadać na niego.", + "Help" : "Pomoc", + "Apps" : "Aplikacje", + "Settings" : "Ustawienia", + "Log out" : "Wyloguj", + "Users" : "Użytkownicy", + "Unknown user" : "Nieznany użytkownik", + "Additional settings" : "Ustawienia dodatkowe", + "%s enter the database username and name." : "Podaj nazwę bazy danych i nazwę użytkownika dla %s.", + "%s enter the database username." : "Podaj nazwę użytkownika dla %s.", + "%s enter the database name." : "Podaj nazwę bazy danych dla %s.", + "%s you may not use dots in the database name" : "Nie możesz używać kropek w nazwie bazy danych %s.", + "MySQL username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych MySQL", + "You need to enter details of an existing account." : "Musisz wprowadzić szczegółowe dane dla istniejącego konta.", + "Oracle connection could not be established" : "Nie można nawiązać połączenia z bazą danych Oracle", + "Oracle username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych Oracle", + "PostgreSQL username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nie jest wspierany i %s nie będzie działać poprawnie na tej platformie. Używasz go na własne ryzyko!", + "For the best results, please consider using a GNU/Linux server instead." : "Aby uzyskać najlepszy efekt, rozważ użycie serwera GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Wydaje się, że ta instancja %s używa PHP dla 32-bitowego środowiska, ponieważ open_basedir został tak skonfigurowany w php.ini. Doprowadzi to do problemów z plikami powyżej 4 GB i jest bardzo niezalecane.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Usuń ustawienie open_basedir w php.ini lub przełącz na PHP 64-bitowe.", + "Set an admin username." : "Ustaw nazwę administratora.", + "Set an admin password." : "Ustaw hasło administratora.", + "Can't create or write into the data directory %s" : "Nie można tworzyć ani zapisywać w katalogu %s", + "Invalid Federated Cloud ID" : "Nieprawidłowy ID Chmury Federacyjnej", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Zaplecze do współdzielenia %s musi implementować interfejs OCP\\Share_Backend", + "Sharing backend %s not found" : "Zaplecze %s do współdzielenia nie zostało znalezione", + "Sharing backend for %s not found" : "Zaplecze do współdzielenia dla %s nie zostało znalezione", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s udostępnił Tobie »%2$s« i chce dodać: ", + "%1$s shared »%2$s« with you and wants to add" : " %1$s udostępnił Tobie »%2$s« i chce dodać", + "»%s« added a note to a file shared with you" : "»%s« dodał notatkę do pliku współdzielonego z Tobą", + "Open »%s«" : "Otwórz »%s«", + "%1$s via %2$s" : "%1$s przez %2$s", + "You are not allowed to share %s" : "Nie możesz udostępnić %s", + "Can’t increase permissions of %s" : "Nie można zwiększyć uprawnień %s", + "Files can’t be shared with delete permissions" : "Pliki nie mogą zostać udostępnione z prawem do usuwania", + "Files can’t be shared with create permissions" : "Pliki nie mogą zostać udostępnione z prawem do tworzenia", + "Expiration date is in the past" : "Data ważności już minęła", + "Can’t set expiration date more than %s days in the future" : "Nie można ustawić daty ważności na dłuższą niż %s dni", + "%1$s shared »%2$s« with you" : "%1$s udostępnił Tobie »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s udostępnił Tobie »%2$s«.", + "Click the button below to open it." : "Kliknij przycisk poniżej, aby otworzyć.", + "The requested share does not exist anymore" : "Żądane współdzielenie już nie istnieje", + "Could not find category \"%s\"" : "Nie można znaleźć kategorii \"%s\"", + "Sunday" : "Niedziela", + "Monday" : "Poniedziałek", + "Tuesday" : "Wtorek", + "Wednesday" : "Środa", + "Thursday" : "Czwartek", + "Friday" : "Piątek", + "Saturday" : "Sobota", + "Sun." : "Nd.", + "Mon." : "Pon.", + "Tue." : "Wt.", + "Wed." : "Śr.", + "Thu." : "Czw.", + "Fri." : "Pt.", + "Sat." : "Sob.", + "Su" : "Nd", + "Mo" : "Pn", + "Tu" : "Wt", + "We" : "Śr", + "Th" : "Czw", + "Fr" : "Pt", + "Sa" : "Sob", + "January" : "Styczeń", + "February" : "Luty", + "March" : "Marzec", + "April" : "Kwiecień", + "May" : "Maj", + "June" : "Czerwiec", + "July" : "Lipiec", + "August" : "Sierpień", + "September" : "Wrzesień", + "October" : "Październik", + "November" : "Listopad", + "December" : "Grudzień", + "Jan." : "Sty.", + "Feb." : "Lut.", + "Mar." : "Mar.", + "Apr." : "Kwi.", + "May." : "Maj", + "Jun." : "Cze.", + "Jul." : "Lip.", + "Aug." : "Sie.", + "Sep." : "Wrz.", + "Oct." : "Paź.", + "Nov." : "Lis.", + "Dec." : "Gru.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "W nazwie użytkownika dozwolone są tylko następujące znaki : \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"", + "A valid username must be provided" : "Należy podać prawidłową nazwę użytkownika", + "Username contains whitespace at the beginning or at the end" : "Nazwa użytkownika zawiera spację na początku albo na końcu", + "Username must not consist of dots only" : "Nazwa użytkownika nie może się składać tylko z kropek", + "Username is invalid because files already exist for this user" : "Nazwa użytkownika jest nieprawidłowa, ponieważ pliki już istnieją dla tego użytkownika", + "A valid password must be provided" : "Należy podać prawidłowe hasło", + "The username is already being used" : "Ta nazwa użytkownika jest już używana", + "Could not create user" : "Nie można utworzyć użytkownika", + "User disabled" : "Użytkownik zablokowany", + "Login canceled by app" : "Logowanie anulowane przez aplikację", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Nie można zainstalować aplikacji \"%1$s\", ponieważ nie są spełnione następujące zależności: %2$s", + "a safe home for all your data" : "bezpieczny dom dla wszystkich danych", + "File is currently busy, please try again later" : "Plik jest obecnie niedostępny, spróbuj później", + "Can't read file" : "Nie można odczytać pliku", + "Application is not enabled" : "Aplikacja nie jest włączona", + "Authentication error" : "Błąd uwierzytelniania", + "Token expired. Please reload page." : "Token wygasł. Przeładuj stronę.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nie zainstalowano sterowników bazy danych (sqlite, mysql lub postgresql).", + "Cannot write into \"config\" directory" : "Nie można zapisać do katalogu \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config. Zobacz %s", + "Cannot write into \"apps\" directory" : "Nie można zapisać do katalogu \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Zwykle można to naprawić, przyznając serwerowi dostęp do zapisu do katalogu aplikacji lub wyłączając aplikację w pliku konfiguracyjnym.", + "Cannot create \"data\" directory" : "Nie mozna utworzyć katalogu \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu głównego. Zobacz %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Uprawnienia można zazwyczaj naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu głównego. Zobacz %s.", + "Setting locale to %s failed" : "Nie udało się zmienić języka na %s", + "Please install one of these locales on your system and restart your webserver." : "Zainstaluj jedną z lokalizacji w swoim systemie i ponownie uruchom serwer WWW.", + "PHP module %s not installed." : "Moduł PHP %s nie jest zainstalowany.", + "Please ask your server administrator to install the module." : "Poproś administratora serwera o zainstalowanie modułu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Ustawienie PHP \"%s\" nie jest ustawione na \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Dostosowanie tego ustawienia w php.ini spowoduje ponowne uruchomienie Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload jest ustawione na \"%s\" zamiast spodziewanej wartości \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Aby rozwiązać ten problem, ustaw mbstring.func_overload na 0 w pliku php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Wymagana wersja dla libxml2 to przynajmniej 2.7.0. Aktualnie zainstalowana jest %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Aby rozwiązać ten problem, zaktualizuj wersję libxml2 i ponownie uruchom serwer WWW.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Wygląda na to, że PHP jest tak ustawione, aby wycinać bloki wklejonych dokumentów. Może to spowodować, że niektóre wbudowane aplikacje będą niedostępne.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Jest to prawdopodobnie spowodowane przez cache lub akcelerator, taki jak Zend OPcache lub eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Moduły PHP zostały zainstalowane, ale nadal brakuje ich na liście?", + "Please ask your server administrator to restart the web server." : "Poproś administratora serwera o ponowne uruchomienie serwera WWW.", + "PostgreSQL >= 9 required" : "Wymagany PostgreSQL >= 9", + "Please upgrade your database version" : "Zaktualizuj wersję bazy danych", + "Your data directory is readable by other users" : "Twój katalog danych jest widoczny dla innych użytkowników", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Zmień uprawnienia na 0770, aby katalog nie był widoczny dla innych użytkowników.", + "Your data directory must be an absolute path" : "Katalog danych musi mieć ścieżkę absolutną", + "Check the value of \"datadirectory\" in your configuration" : "Sprawdź wartość \"datadirectory\" w swojej konfiguracji", + "Your data directory is invalid" : "Katalog danych jest nieprawidłowy", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Upewnij się, że w katalogu \"data\" znajduje się plik o nazwie \".ocdata\".", + "Action \"%s\" not supported or implemented." : "Akcja \"%s\" jest niewspierana lub niezaimplementowana.", + "Authentication failed, wrong token or provider ID given" : "Uwierzytelnienie nie powiodło się, podano nieprawidłowy token lub ID dostawcy", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Brak parametrów w celu uzupełnienia żądania. Brakujące parametry: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" jest już używany przez dostawcę chmury federacyjnej \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Dostawca Chmury Federacyjnej o ID: \"%s\" nie istnieje.", + "Could not obtain lock type %d on \"%s\"." : "Nie można uzyskać blokady typu %d na \"%s\".", + "Storage unauthorized. %s" : "Magazyn nieautoryzowany. %s", + "Storage incomplete configuration. %s" : "Niekompletna konfiguracja magazynu. %s", + "Storage connection error. %s" : "Błąd połączenia z magazynem. %s", + "Storage is temporarily not available" : "Magazyn jest tymczasowo niedostępny", + "Storage connection timeout. %s" : "Limit czasu połączenia do magazynu. %s", + "Following databases are supported: %s" : "Obsługiwane są następujące bazy danych: %s", + "Following platforms are supported: %s" : "Obsługiwane są następujące platformy: %s", + "Overview" : "Przegląd", + "Basic settings" : "Ustawienia podstawowe", + "Sharing" : "Udostępnianie", + "Security" : "Bezpieczeństwo", + "Groupware" : "Praca grupowa", + "Personal info" : "Informacje osobiste", + "Mobile & desktop" : "Mobilne i stacjonarne", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW uprawnienia do zapisu do katalogu apps lub wyłączając sklep aplikacji w pliku config. Patrz %s" +}, +"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/pl.json b/docker/overlays/nextcloud/html/lib/l10n/pl.json new file mode 100644 index 0000000..ff360af --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pl.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nie można zapisać do katalogu \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Jeśli, albo wolisz zachować plik config.php tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true.", + "See %s" : "Zobacz %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Jeśli, albo wolisz zachować plik config.php tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true. Zobacz %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.", + "Sample configuration detected" : "Wykryto przykładową konfigurację", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php", + "Other activities" : "Inne czynności", + "%1$s and %2$s" : "%1$s i %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s i %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s i %5$s", + "Education Edition" : "Edycja edukacyjna", + "Enterprise bundle" : "Pakiet dla firm", + "Groupware bundle" : "Pakiet do pracy grupowej", + "Hub bundle" : "Pakiet centralny", + "Social sharing bundle" : "Pakiet udostępniania społecznościowego", + "PHP %s or higher is required." : "Wymagane jest PHP %s lub wyższe.", + "PHP with a version lower than %s is required." : "Wymagane jest PHP z wersją niższą niż %s.", + "%sbit or higher PHP required." : "%sbit lub wymagane wyższe PHP.", + "The following architectures are supported: %s" : "Obsługiwane są następujące architektury: %s", + "The following databases are supported: %s" : "Obsługiwane są następujące bazy danych: %s", + "The command line tool %s could not be found" : "Nie można znaleźć narzędzia wiersza poleceń %s", + "The library %s is not available." : "Biblioteka %s nie jest dostępna.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Wymagana jest biblioteka %1$s z wersją wyższą niż %2$s - dostępna wersja %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Wymagana jest biblioteka %1$s w wersji niższej niż %2$s - dostępna wersja %3$s.", + "The following platforms are supported: %s" : "Obsługiwane są następujące platformy: %s", + "Server version %s or higher is required." : "Wymagana jest wersja serwera %s lub wyższa.", + "Server version %s or lower is required." : "Wymagana jest wersja serwera %s lub niższa.", + "Logged in user must be an admin or sub admin" : "Zalogowany użytkownik musi być administratorem lub współadministratorem", + "Logged in user must be an admin" : "Zalogowany użytkownik musi być administratorem", + "Wiping of device %s has started" : "Rozpoczęto czyszczenie urządzenia %s", + "Wiping of device »%s« has started" : "Rozpoczęto czyszczenie urządzenia »%s«", + "»%s« started remote wipe" : "»%s« rozpoczęło zdalne czyszczenie", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Urządzenie lub aplikacja »%s« uruchomiło proces zdalnego czyszczenia. Otrzymasz kolejny e-mail po zakończeniu procesu", + "Wiping of device %s has finished" : "Zakończono czyszczenie urządzenia %s", + "Wiping of device »%s« has finished" : "Zakończono czyszczenie urządzenia »%s«", + "»%s« finished remote wipe" : "»%s« zakończył zdalne czyszczenie", + "Device or application »%s« has finished the remote wipe process." : "Urządzenie lub aplikacja »%s« zakończyło proces zdalnego czyszczenia.", + "Remote wipe started" : "Rozpoczęto zdalne czyszczenie", + "A remote wipe was started on device %s" : "Zdalne czyszczenie zostało uruchomione na urządzeniu %s", + "Remote wipe finished" : "Zdalne czyszczenie zakończone", + "The remote wipe on %s has finished" : "Zakończono zdalne czyszczenie na %s", + "Authentication" : "Uwierzytelnienie", + "Unknown filetype" : "Nieznany typ pliku", + "Invalid image" : "Nieprawidłowy obraz", + "Avatar image is not square" : "Obraz awatara nie jest kwadratowy", + "today" : "dzisiaj", + "tomorrow" : "jutro", + "yesterday" : "wczoraj", + "_in %n day_::_in %n days_" : ["za %n dzień","za %n dni","za %n dni","za %n dni"], + "_%n day ago_::_%n days ago_" : ["%d dzień temu","%n dni temu","%n dni temu","%n dni temu"], + "next month" : "następny miesiąc", + "last month" : "w zeszłym miesiącu", + "_in %n month_::_in %n months_" : ["za %n miesiąc","za %n miesiące","za %n miesięcy","za %n miesięcy"], + "_%n month ago_::_%n months ago_" : ["%n miesiąc temu","%n miesiące temu","%n miesięcy temu","%n miesięcy temu"], + "next year" : "następny rok", + "last year" : "w zeszłym roku", + "_in %n year_::_in %n years_" : ["za %n rok","za %n lata","za %n lat","za %n lat"], + "_%n year ago_::_%n years ago_" : ["%n rok temu","%n lata temu","%n lat temu","%n lat temu"], + "_in %n hour_::_in %n hours_" : ["za %n godzinę","za %n godziny","za %n godzin","za %n godziny"], + "_%n hour ago_::_%n hours ago_" : ["%n godzinę temu","%n godziny temu","%n godzin temu","%n godzin temu"], + "_in %n minute_::_in %n minutes_" : ["za %n minutę","za %n minuty","za %n minut","za %n minuty"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuta temu","%n minuty temu","%n minut temu","%n minut temu"], + "in a few seconds" : "za kilka sekund", + "seconds ago" : "przed chwilą", + "Empty file" : "Pusty plik", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Moduł o ID: %s nie istnieje. Włącz go w ustawieniach aplikacji lub skontaktuj się z administratorem.", + "File name is a reserved word" : "Nazwa pliku jest zarezerwowana", + "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", + "File name is too long" : "Nazwa pliku jest za długa", + "Dot files are not allowed" : "Pliki z kropką są nie dozwolone", + "Empty filename is not allowed" : "Pusta nazwa pliku jest niedozwolona", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikacji \"%s\" nie można zainstalować, ponieważ nie można odczytać pliku appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikacji \"%s\" nie można zainstalować, ponieważ nie jest kompatybilna z tą wersją serwera.", + "__language_name__" : "Polski", + "This is an automatically sent email, please do not reply." : "To jest automatycznie wysłany e-mail, proszę nie odpowiadać na niego.", + "Help" : "Pomoc", + "Apps" : "Aplikacje", + "Settings" : "Ustawienia", + "Log out" : "Wyloguj", + "Users" : "Użytkownicy", + "Unknown user" : "Nieznany użytkownik", + "Additional settings" : "Ustawienia dodatkowe", + "%s enter the database username and name." : "Podaj nazwę bazy danych i nazwę użytkownika dla %s.", + "%s enter the database username." : "Podaj nazwę użytkownika dla %s.", + "%s enter the database name." : "Podaj nazwę bazy danych dla %s.", + "%s you may not use dots in the database name" : "Nie możesz używać kropek w nazwie bazy danych %s.", + "MySQL username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych MySQL", + "You need to enter details of an existing account." : "Musisz wprowadzić szczegółowe dane dla istniejącego konta.", + "Oracle connection could not be established" : "Nie można nawiązać połączenia z bazą danych Oracle", + "Oracle username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych Oracle", + "PostgreSQL username and/or password not valid" : "Zła nazwa użytkownika i/lub hasło do bazy danych PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nie jest wspierany i %s nie będzie działać poprawnie na tej platformie. Używasz go na własne ryzyko!", + "For the best results, please consider using a GNU/Linux server instead." : "Aby uzyskać najlepszy efekt, rozważ użycie serwera GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Wydaje się, że ta instancja %s używa PHP dla 32-bitowego środowiska, ponieważ open_basedir został tak skonfigurowany w php.ini. Doprowadzi to do problemów z plikami powyżej 4 GB i jest bardzo niezalecane.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Usuń ustawienie open_basedir w php.ini lub przełącz na PHP 64-bitowe.", + "Set an admin username." : "Ustaw nazwę administratora.", + "Set an admin password." : "Ustaw hasło administratora.", + "Can't create or write into the data directory %s" : "Nie można tworzyć ani zapisywać w katalogu %s", + "Invalid Federated Cloud ID" : "Nieprawidłowy ID Chmury Federacyjnej", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Zaplecze do współdzielenia %s musi implementować interfejs OCP\\Share_Backend", + "Sharing backend %s not found" : "Zaplecze %s do współdzielenia nie zostało znalezione", + "Sharing backend for %s not found" : "Zaplecze do współdzielenia dla %s nie zostało znalezione", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s udostępnił Tobie »%2$s« i chce dodać: ", + "%1$s shared »%2$s« with you and wants to add" : " %1$s udostępnił Tobie »%2$s« i chce dodać", + "»%s« added a note to a file shared with you" : "»%s« dodał notatkę do pliku współdzielonego z Tobą", + "Open »%s«" : "Otwórz »%s«", + "%1$s via %2$s" : "%1$s przez %2$s", + "You are not allowed to share %s" : "Nie możesz udostępnić %s", + "Can’t increase permissions of %s" : "Nie można zwiększyć uprawnień %s", + "Files can’t be shared with delete permissions" : "Pliki nie mogą zostać udostępnione z prawem do usuwania", + "Files can’t be shared with create permissions" : "Pliki nie mogą zostać udostępnione z prawem do tworzenia", + "Expiration date is in the past" : "Data ważności już minęła", + "Can’t set expiration date more than %s days in the future" : "Nie można ustawić daty ważności na dłuższą niż %s dni", + "%1$s shared »%2$s« with you" : "%1$s udostępnił Tobie »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s udostępnił Tobie »%2$s«.", + "Click the button below to open it." : "Kliknij przycisk poniżej, aby otworzyć.", + "The requested share does not exist anymore" : "Żądane współdzielenie już nie istnieje", + "Could not find category \"%s\"" : "Nie można znaleźć kategorii \"%s\"", + "Sunday" : "Niedziela", + "Monday" : "Poniedziałek", + "Tuesday" : "Wtorek", + "Wednesday" : "Środa", + "Thursday" : "Czwartek", + "Friday" : "Piątek", + "Saturday" : "Sobota", + "Sun." : "Nd.", + "Mon." : "Pon.", + "Tue." : "Wt.", + "Wed." : "Śr.", + "Thu." : "Czw.", + "Fri." : "Pt.", + "Sat." : "Sob.", + "Su" : "Nd", + "Mo" : "Pn", + "Tu" : "Wt", + "We" : "Śr", + "Th" : "Czw", + "Fr" : "Pt", + "Sa" : "Sob", + "January" : "Styczeń", + "February" : "Luty", + "March" : "Marzec", + "April" : "Kwiecień", + "May" : "Maj", + "June" : "Czerwiec", + "July" : "Lipiec", + "August" : "Sierpień", + "September" : "Wrzesień", + "October" : "Październik", + "November" : "Listopad", + "December" : "Grudzień", + "Jan." : "Sty.", + "Feb." : "Lut.", + "Mar." : "Mar.", + "Apr." : "Kwi.", + "May." : "Maj", + "Jun." : "Cze.", + "Jul." : "Lip.", + "Aug." : "Sie.", + "Sep." : "Wrz.", + "Oct." : "Paź.", + "Nov." : "Lis.", + "Dec." : "Gru.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "W nazwie użytkownika dozwolone są tylko następujące znaki : \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"", + "A valid username must be provided" : "Należy podać prawidłową nazwę użytkownika", + "Username contains whitespace at the beginning or at the end" : "Nazwa użytkownika zawiera spację na początku albo na końcu", + "Username must not consist of dots only" : "Nazwa użytkownika nie może się składać tylko z kropek", + "Username is invalid because files already exist for this user" : "Nazwa użytkownika jest nieprawidłowa, ponieważ pliki już istnieją dla tego użytkownika", + "A valid password must be provided" : "Należy podać prawidłowe hasło", + "The username is already being used" : "Ta nazwa użytkownika jest już używana", + "Could not create user" : "Nie można utworzyć użytkownika", + "User disabled" : "Użytkownik zablokowany", + "Login canceled by app" : "Logowanie anulowane przez aplikację", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Nie można zainstalować aplikacji \"%1$s\", ponieważ nie są spełnione następujące zależności: %2$s", + "a safe home for all your data" : "bezpieczny dom dla wszystkich danych", + "File is currently busy, please try again later" : "Plik jest obecnie niedostępny, spróbuj później", + "Can't read file" : "Nie można odczytać pliku", + "Application is not enabled" : "Aplikacja nie jest włączona", + "Authentication error" : "Błąd uwierzytelniania", + "Token expired. Please reload page." : "Token wygasł. Przeładuj stronę.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nie zainstalowano sterowników bazy danych (sqlite, mysql lub postgresql).", + "Cannot write into \"config\" directory" : "Nie można zapisać do katalogu \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config. Zobacz %s", + "Cannot write into \"apps\" directory" : "Nie można zapisać do katalogu \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Zwykle można to naprawić, przyznając serwerowi dostęp do zapisu do katalogu aplikacji lub wyłączając aplikację w pliku konfiguracyjnym.", + "Cannot create \"data\" directory" : "Nie mozna utworzyć katalogu \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu głównego. Zobacz %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Uprawnienia można zazwyczaj naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu głównego. Zobacz %s.", + "Setting locale to %s failed" : "Nie udało się zmienić języka na %s", + "Please install one of these locales on your system and restart your webserver." : "Zainstaluj jedną z lokalizacji w swoim systemie i ponownie uruchom serwer WWW.", + "PHP module %s not installed." : "Moduł PHP %s nie jest zainstalowany.", + "Please ask your server administrator to install the module." : "Poproś administratora serwera o zainstalowanie modułu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Ustawienie PHP \"%s\" nie jest ustawione na \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Dostosowanie tego ustawienia w php.ini spowoduje ponowne uruchomienie Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload jest ustawione na \"%s\" zamiast spodziewanej wartości \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Aby rozwiązać ten problem, ustaw mbstring.func_overload na 0 w pliku php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Wymagana wersja dla libxml2 to przynajmniej 2.7.0. Aktualnie zainstalowana jest %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Aby rozwiązać ten problem, zaktualizuj wersję libxml2 i ponownie uruchom serwer WWW.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Wygląda na to, że PHP jest tak ustawione, aby wycinać bloki wklejonych dokumentów. Może to spowodować, że niektóre wbudowane aplikacje będą niedostępne.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Jest to prawdopodobnie spowodowane przez cache lub akcelerator, taki jak Zend OPcache lub eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Moduły PHP zostały zainstalowane, ale nadal brakuje ich na liście?", + "Please ask your server administrator to restart the web server." : "Poproś administratora serwera o ponowne uruchomienie serwera WWW.", + "PostgreSQL >= 9 required" : "Wymagany PostgreSQL >= 9", + "Please upgrade your database version" : "Zaktualizuj wersję bazy danych", + "Your data directory is readable by other users" : "Twój katalog danych jest widoczny dla innych użytkowników", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Zmień uprawnienia na 0770, aby katalog nie był widoczny dla innych użytkowników.", + "Your data directory must be an absolute path" : "Katalog danych musi mieć ścieżkę absolutną", + "Check the value of \"datadirectory\" in your configuration" : "Sprawdź wartość \"datadirectory\" w swojej konfiguracji", + "Your data directory is invalid" : "Katalog danych jest nieprawidłowy", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Upewnij się, że w katalogu \"data\" znajduje się plik o nazwie \".ocdata\".", + "Action \"%s\" not supported or implemented." : "Akcja \"%s\" jest niewspierana lub niezaimplementowana.", + "Authentication failed, wrong token or provider ID given" : "Uwierzytelnienie nie powiodło się, podano nieprawidłowy token lub ID dostawcy", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Brak parametrów w celu uzupełnienia żądania. Brakujące parametry: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" jest już używany przez dostawcę chmury federacyjnej \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Dostawca Chmury Federacyjnej o ID: \"%s\" nie istnieje.", + "Could not obtain lock type %d on \"%s\"." : "Nie można uzyskać blokady typu %d na \"%s\".", + "Storage unauthorized. %s" : "Magazyn nieautoryzowany. %s", + "Storage incomplete configuration. %s" : "Niekompletna konfiguracja magazynu. %s", + "Storage connection error. %s" : "Błąd połączenia z magazynem. %s", + "Storage is temporarily not available" : "Magazyn jest tymczasowo niedostępny", + "Storage connection timeout. %s" : "Limit czasu połączenia do magazynu. %s", + "Following databases are supported: %s" : "Obsługiwane są następujące bazy danych: %s", + "Following platforms are supported: %s" : "Obsługiwane są następujące platformy: %s", + "Overview" : "Przegląd", + "Basic settings" : "Ustawienia podstawowe", + "Sharing" : "Udostępnianie", + "Security" : "Bezpieczeństwo", + "Groupware" : "Praca grupowa", + "Personal info" : "Informacje osobiste", + "Mobile & desktop" : "Mobilne i stacjonarne", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Zwykle można to naprawić, nadając serwerowi WWW uprawnienia do zapisu do katalogu apps lub wyłączając sklep aplikacji w pliku config. Patrz %s" +},"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ps.js b/docker/overlays/nextcloud/html/lib/l10n/ps.js new file mode 100644 index 0000000..429e3a3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ps.js @@ -0,0 +1,15 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "شسیب", + "This can usually be fixed by giving the webserver write access to the config directory" : "شسیب", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "شسیب", + "Unknown filetype" : "د فایل ډول نامعلوم دی", + "Invalid image" : "انځور سم نه دی", + "Help" : "مرسته", + "Apps" : "اپلېکشنونه", + "Settings" : "سمونې", + "Users" : "کارنان", + "Storage is temporarily not available" : "ذخیره د لنډې مودې لپاره نشته" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ps.json b/docker/overlays/nextcloud/html/lib/l10n/ps.json new file mode 100644 index 0000000..45ca4c2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ps.json @@ -0,0 +1,13 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "شسیب", + "This can usually be fixed by giving the webserver write access to the config directory" : "شسیب", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "شسیب", + "Unknown filetype" : "د فایل ډول نامعلوم دی", + "Invalid image" : "انځور سم نه دی", + "Help" : "مرسته", + "Apps" : "اپلېکشنونه", + "Settings" : "سمونې", + "Users" : "کارنان", + "Storage is temporarily not available" : "ذخیره د لنډې مودې لپاره نشته" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/pt_BR.js b/docker/overlays/nextcloud/html/lib/l10n/pt_BR.js new file mode 100644 index 0000000..9941630 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pt_BR.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Não é possível gravar no diretório \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Isso geralmente pode ser corrigido dando o acesso de escritura ao webserver para o diretório de configuração", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, se você preferir manter o arquivo config.php somente para leitura, defina a opção \"config_is_read_only\" como true.", + "See %s" : "Ver %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Geralmente, isso pode ser corrigido concedendo ao servidor web acesso de gravação ao diretório de configuração.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, se você preferir manter o arquivo config.php somente para leitura, defina a opção \"config_is_read_only\" como true. Veja %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os arquivos do aplicativo %1$s não foram instalados corretamente. Certifique-se que é uma versão compatível com seu servidor.", + "Sample configuration detected" : "Configuração de exemplo detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de exemplo foi copiada. Isso pode terminar sua instalação e não é suportado. Por favor leia a documentação antes de realizar mudanças no config.php", + "Other activities" : "Outras atividades", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edição Educativa", + "Enterprise bundle" : "Pacote Enterprise", + "Groupware bundle" : "Pacote Groupware", + "Hub bundle" : "Pacote de hub", + "Social sharing bundle" : "Pacote de compartilhamento social", + "PHP %s or higher is required." : "PHP %s ou superior é requerido.", + "PHP with a version lower than %s is required." : "É requerida uma versão PHP mais antiga que a %s .", + "%sbit or higher PHP required." : "%sbit ou PHP maior é requerido.", + "The following architectures are supported: %s" : "As seguintes plataformas são suportadas: %s", + "The following databases are supported: %s" : "Os seguintes bancos de dados são suportados: %s", + "The command line tool %s could not be found" : "A ferramenta de linha de comando %s não pôde ser encontrada", + "The library %s is not available." : "A biblioteca %s não está disponível.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Uma biblioteca %1$s com uma versão maior que %2$s é requerida - versão disponível %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Uma biblioteca %1$s com uma versão abaixo de%2$s é requerida - versão disponível %3$s.", + "The following platforms are supported: %s" : "As seguintes plataformas são suportadas: %s", + "Server version %s or higher is required." : "É requerido um servidor da versão %s ou superior.", + "Server version %s or lower is required." : "É requerido um servidor da versão %s ou abaixo.", + "Logged in user must be an admin or sub admin" : "O usuário conectado deve ser um administrador ou subadministrador", + "Logged in user must be an admin" : "O usuário conectado deve ser um administrador", + "Wiping of device %s has started" : "Limpeza do dispositivo %s iniciou", + "Wiping of device »%s« has started" : "A limpeza do dispositivo »%s« terminou", + "»%s« started remote wipe" : "»%s« iniciou a limpeza remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "O dispositivo ou aplicativo »%s« iniciou o processo de limpeza remota. Você receberá outro e-mail assim que terminar", + "Wiping of device %s has finished" : "Limpeza do dispositivo %s terminou", + "Wiping of device »%s« has finished" : "Limpeza do dispositivo »%s« terminou", + "»%s« finished remote wipe" : "»%s« terminou a limpeza remota", + "Device or application »%s« has finished the remote wipe process." : "O dispositivo ou aplicativo »%s« terminou o processo de limpeza remota.", + "Remote wipe started" : "Limpeza remota iniciada", + "A remote wipe was started on device %s" : "Uma limpeza remota iniciou no dispositivo %s", + "Remote wipe finished" : "Limpeza remota terminada", + "The remote wipe on %s has finished" : "A limpeza remota em %s terminou", + "Authentication" : "Autenticação", + "Unknown filetype" : "Tipo de arquivo desconhecido", + "Invalid image" : "Imagem inválida", + "Avatar image is not square" : "A imagem do avatar não é quadrada", + "today" : "hoje", + "tomorrow" : "amanhã", + "yesterday" : "ontem", + "_in %n day_::_in %n days_" : ["em %n dia","em %n dias"], + "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], + "next month" : "Mês que vem", + "last month" : "último mês", + "_in %n month_::_in %n months_" : ["em %n mês","em %n meses"], + "_%n month ago_::_%n months ago_" : ["há %n mês atrás","há %n meses"], + "next year" : "ano que vem", + "last year" : "último ano", + "_in %n year_::_in %n years_" : ["em %n ano","em %n anos"], + "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], + "_in %n hour_::_in %n hours_" : ["em %n hora","em %n horas"], + "_%n hour ago_::_%n hours ago_" : ["há %n hora atrás","há %n horas"], + "_in %n minute_::_in %n minutes_" : ["em %n minuto","em %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["há %n minuto atrás","há %n minutos"], + "in a few seconds" : "Em alguns segundos", + "seconds ago" : "segundos atrás", + "Empty file" : "Arquivo vazio", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "O módulo com a ID: %s não existe. Por favor, habilite-o nas configurações de seu aplicativo ou contacte o administrador.", + "File name is a reserved word" : "O nome do arquivo é uma palavra reservada", + "File name contains at least one invalid character" : "O nome do arquivo contém pelo menos um caracter inválido", + "File name is too long" : "O nome do arquivo é muito longo", + "Dot files are not allowed" : "Arquivos Dot não são permitidos", + "Empty filename is not allowed" : "Nome vazio para arquivo não é permitido.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "O aplicativo \"%s\" não pode ser instalado pois o arquivo appinfo não pôde ser lido.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "O aplicativo \"%s\" não pode ser instalado pois não é compatível com a versão do servidor.", + "__language_name__" : "Português Brasileiro", + "This is an automatically sent email, please do not reply." : "Este e-mail é enviado automaticamente. Por favor, não responda.", + "Help" : "Ajuda", + "Apps" : "Aplicativos", + "Settings" : "Configurações", + "Log out" : "Sair", + "Users" : "Usuários", + "Unknown user" : "Usuário desconhecido", + "Additional settings" : "Configurações adicionais", + "%s enter the database username and name." : "%s insira o nome de usuário e o nome do banco de dados.", + "%s enter the database username." : "%s insira o nome de usuário do banco de dados.", + "%s enter the database name." : "%s insira o nome do banco de dados.", + "%s you may not use dots in the database name" : "%s você não pode usar pontos no nome do banco de dados", + "MySQL username and/or password not valid" : "Nome de usuário e/ou senha do MySQL inválidos", + "You need to enter details of an existing account." : "Você necessita entrar detalhes de uma conta existente.", + "Oracle connection could not be established" : "Conexão Oracle não pôde ser estabelecida", + "Oracle username and/or password not valid" : "Nome de usuário e/ou senha Oracle inválidos", + "PostgreSQL username and/or password not valid" : "Nome de usuário e/ou senha PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X não é suportado e %s não funcionará corretamente nesta plataforma. Use-o por sua conta e risco!", + "For the best results, please consider using a GNU/Linux server instead." : "Para obter melhores resultados, por favor considere o uso de um servidor GNU/Linux em seu lugar.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Aparentemente a instância %s está rodando em um ambiente PHP de 32 bits e o open_basedir foi configurado no php.ini. Isto pode gerar problemas com arquivos maiores que 4GB e é altamente não recomendável.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, remova a configuração de open_basedir de seu php.ini ou mude o PHP para 64bit.", + "Set an admin username." : "Defina um nome do usuário administrador.", + "Set an admin password." : "Defina uma senha para o administrador.", + "Can't create or write into the data directory %s" : "Não foi possível criar ou gravar no diretório de dados %s", + "Invalid Federated Cloud ID" : "ID inválida de Nuvem Federada", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "A plataforma de compartilhamento %s deve implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Plataforma de serviço de compartilhamento %s não encontrada", + "Sharing backend for %s not found" : "Plataforma de compartilhamento para %s não foi encontrada", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartilhou »%2$s« com você e quer adicionar:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s compartilhou »%2$s« com você e quer adicionar", + "»%s« added a note to a file shared with you" : "»%s« adicionou uma anotação num arquivo compartilhado com você", + "Open »%s«" : "Abrir »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Você não tem permissão para compartilhar %s", + "Can’t increase permissions of %s" : "Não posso aumentar as permissões de %s", + "Files can’t be shared with delete permissions" : "Os arquivos não podem ser compartilhados com permissões de exclusão", + "Files can’t be shared with create permissions" : "Os arquivos não podem ser compartilhados com permissões de criação", + "Expiration date is in the past" : "Data de expiração está no passado", + "Can’t set expiration date more than %s days in the future" : "Não é possível definir a expiração mais do que %s dias no futuro", + "%1$s shared »%2$s« with you" : "%1$s compartilhou »%2$s« com você", + "%1$s shared »%2$s« with you." : "%1$s compartilhou »%2$s« com você.", + "Click the button below to open it." : "Clique no botão abaixo para abri-lo.", + "The requested share does not exist anymore" : "O compartilhamento solicitado não existe mais", + "Could not find category \"%s\"" : "Impossível localizar a categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Segunda-feira", + "Tuesday" : "Terça-feira", + "Wednesday" : "Quarta-feira", + "Thursday" : "Quinta-feira", + "Friday" : "Sexta-feira", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Seg.", + "Tue." : "Ter.", + "Wed." : "Qua.", + "Thu." : "Qui.", + "Fri." : "Sex.", + "Sat." : "Sab.", + "Su" : "Su", + "Mo" : "Se", + "Tu" : "Te", + "We" : "Qu", + "Th" : "Qu", + "Fr" : "Se", + "Sa" : "Sa", + "January" : "Janeiro", + "February" : "Fevereiro", + "March" : "Março", + "April" : "Abril", + "May" : "Maio", + "June" : "Junho", + "July" : "Julho", + "August" : "Agosto", + "September" : "Setembro", + "October" : "Outubro", + "November" : "Novembro", + "December" : "Dezembro", + "Jan." : "Jan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Out.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Um nome de usuário válido deve ser fornecido", + "Username contains whitespace at the beginning or at the end" : "O nome de usuário contém espaço em branco no início ou no fim", + "Username must not consist of dots only" : "Nome do usuário não pode consistir de pontos somente", + "Username is invalid because files already exist for this user" : "O nome de usuário é inválido porque já exstem arquivos para este usuário", + "A valid password must be provided" : "Uma senha válida deve ser fornecida", + "The username is already being used" : "Este nome de usuário já está em uso", + "Could not create user" : "Não foi possível criar o usuário", + "User disabled" : "Usuário desativado", + "Login canceled by app" : "Login cancelado pelo aplicativo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "O aplicativo \"%1$s\" não pode ser instalado devido à estas dependências: %2$s", + "a safe home for all your data" : "Um lar seguro para todos os seus dados", + "File is currently busy, please try again later" : "O arquivo está ocupado, tente novamente mais tarde", + "Can't read file" : "Não foi possível ler arquivo", + "Application is not enabled" : "O aplicativo não está habilitado", + "Authentication error" : "Erro de autenticação", + "Token expired. Please reload page." : "O token expirou. Por favor recarregue a página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nenhum driver de banco de dados (sqlite, mysql ou postgresql) instalado.", + "Cannot write into \"config\" directory" : "Não foi possível gravar no diretório \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escritura no diretório config. Veja %s", + "Cannot write into \"apps\" directory" : "Não foi possível gravar no diretório \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Geralmente, isso pode ser corrigido concedendo ao servidor web acesso de gravação ao diretório de aplicativos ou desativando a loja de aplicativos no arquivo de configuração.", + "Cannot create \"data\" directory" : "Não foi possível criar o diretório \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escrita no diretório raiz. Veja %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "As permissões normalmente podem ser corrigidas dando permissão de escrita do diretório raiz para o servidor web. Veja %s.", + "Setting locale to %s failed" : "Falha ao configurar localização para %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor, defina uma dessas localizações em seu sistema e reinicie o seu servidor web.", + "PHP module %s not installed." : "Módulo PHP %s não instalado.", + "Please ask your server administrator to install the module." : "Por favor, peça ao seu administrador do servidor para instalar o módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "Configuração PHP \"%s\" não está configurado para \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar a configuração no php.ini fará com que o Nextcloud execute novamente", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está definido para \"%s\" ao invés do valor esperado \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corrigir esse problema defina mbstring.func_overload para 0 em seu php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "A libxml2 2.7.0 é a versão mínima requerida. Atualmente a versão %s está instalada.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema, atualize a versão da sua libxml2 e reinicie seu servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP aparentemente está configurado para retirar blocos doc inline. Isso fará com que vários aplicativos do núcleo fiquem inacessíveis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isso provavelmente é causado por um cache/acelerador, como Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Módulos do PHP foram instalados, mas eles ainda estão listados como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor peça ao administrador do servidor para reiniciar o servidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requirido", + "Please upgrade your database version" : "Por favor atualize sua versão do banco de dados", + "Your data directory is readable by other users" : "O diretório de dados está legível para outros usuários", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor altere as permissões para 0770 para que o diretório não possa ser lido por outros usuários.", + "Your data directory must be an absolute path" : "O diretório de dados deve ser um caminho absoluto", + "Check the value of \"datadirectory\" in your configuration" : "Verifique o valor do \"datadirectory\" na sua configuração", + "Your data directory is invalid" : "Seu diretório de dados é inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assegure-se que exista um arquivo chamado \".ocdata\" na raiz do diretório \"data\".", + "Action \"%s\" not supported or implemented." : "Ação \"%s\" não suportada ou implementada.", + "Authentication failed, wrong token or provider ID given" : "Falha na autenticação, token ou ID do provedor errados", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parâmetros ausentes para concluir a solicitação: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "A ID \"%1$s\" já é usada pelo provedor de nuvem federada \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Provedor da Federação de Nuvem com ID: \"%s\" não existe.", + "Could not obtain lock type %d on \"%s\"." : "Não foi possível obter tipo de bloqueio %d em \"%s\".", + "Storage unauthorized. %s" : "Armazenamento não autorizado. %s", + "Storage incomplete configuration. %s" : "Configuração incompleta do armazenamento. %s", + "Storage connection error. %s" : "Erro na conexão de armazenamento. %s", + "Storage is temporarily not available" : "Armazenamento temporariamente indisponível", + "Storage connection timeout. %s" : "Atingido o tempo limite de conexão ao armazenamento. %s", + "Following databases are supported: %s" : "Os seguintes bancos de dados são suportados: %s", + "Following platforms are supported: %s" : "As seguintes plataformas são suportadas: %s", + "Overview" : "Visão geral", + "Basic settings" : "Configurações básicas", + "Sharing" : "Compartilhamento", + "Security" : "Segurança", + "Groupware" : "Groupware", + "Personal info" : "Informação pessoal", + "Mobile & desktop" : "Móvel & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escrita no diretório apps ou desabilitando a appstore no arquivo de configuração. Veja %s" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/pt_BR.json b/docker/overlays/nextcloud/html/lib/l10n/pt_BR.json new file mode 100644 index 0000000..1c48c52 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pt_BR.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Não é possível gravar no diretório \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Isso geralmente pode ser corrigido dando o acesso de escritura ao webserver para o diretório de configuração", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, se você preferir manter o arquivo config.php somente para leitura, defina a opção \"config_is_read_only\" como true.", + "See %s" : "Ver %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Geralmente, isso pode ser corrigido concedendo ao servidor web acesso de gravação ao diretório de configuração.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, se você preferir manter o arquivo config.php somente para leitura, defina a opção \"config_is_read_only\" como true. Veja %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os arquivos do aplicativo %1$s não foram instalados corretamente. Certifique-se que é uma versão compatível com seu servidor.", + "Sample configuration detected" : "Configuração de exemplo detectada", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de exemplo foi copiada. Isso pode terminar sua instalação e não é suportado. Por favor leia a documentação antes de realizar mudanças no config.php", + "Other activities" : "Outras atividades", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edição Educativa", + "Enterprise bundle" : "Pacote Enterprise", + "Groupware bundle" : "Pacote Groupware", + "Hub bundle" : "Pacote de hub", + "Social sharing bundle" : "Pacote de compartilhamento social", + "PHP %s or higher is required." : "PHP %s ou superior é requerido.", + "PHP with a version lower than %s is required." : "É requerida uma versão PHP mais antiga que a %s .", + "%sbit or higher PHP required." : "%sbit ou PHP maior é requerido.", + "The following architectures are supported: %s" : "As seguintes plataformas são suportadas: %s", + "The following databases are supported: %s" : "Os seguintes bancos de dados são suportados: %s", + "The command line tool %s could not be found" : "A ferramenta de linha de comando %s não pôde ser encontrada", + "The library %s is not available." : "A biblioteca %s não está disponível.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Uma biblioteca %1$s com uma versão maior que %2$s é requerida - versão disponível %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Uma biblioteca %1$s com uma versão abaixo de%2$s é requerida - versão disponível %3$s.", + "The following platforms are supported: %s" : "As seguintes plataformas são suportadas: %s", + "Server version %s or higher is required." : "É requerido um servidor da versão %s ou superior.", + "Server version %s or lower is required." : "É requerido um servidor da versão %s ou abaixo.", + "Logged in user must be an admin or sub admin" : "O usuário conectado deve ser um administrador ou subadministrador", + "Logged in user must be an admin" : "O usuário conectado deve ser um administrador", + "Wiping of device %s has started" : "Limpeza do dispositivo %s iniciou", + "Wiping of device »%s« has started" : "A limpeza do dispositivo »%s« terminou", + "»%s« started remote wipe" : "»%s« iniciou a limpeza remota", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "O dispositivo ou aplicativo »%s« iniciou o processo de limpeza remota. Você receberá outro e-mail assim que terminar", + "Wiping of device %s has finished" : "Limpeza do dispositivo %s terminou", + "Wiping of device »%s« has finished" : "Limpeza do dispositivo »%s« terminou", + "»%s« finished remote wipe" : "»%s« terminou a limpeza remota", + "Device or application »%s« has finished the remote wipe process." : "O dispositivo ou aplicativo »%s« terminou o processo de limpeza remota.", + "Remote wipe started" : "Limpeza remota iniciada", + "A remote wipe was started on device %s" : "Uma limpeza remota iniciou no dispositivo %s", + "Remote wipe finished" : "Limpeza remota terminada", + "The remote wipe on %s has finished" : "A limpeza remota em %s terminou", + "Authentication" : "Autenticação", + "Unknown filetype" : "Tipo de arquivo desconhecido", + "Invalid image" : "Imagem inválida", + "Avatar image is not square" : "A imagem do avatar não é quadrada", + "today" : "hoje", + "tomorrow" : "amanhã", + "yesterday" : "ontem", + "_in %n day_::_in %n days_" : ["em %n dia","em %n dias"], + "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], + "next month" : "Mês que vem", + "last month" : "último mês", + "_in %n month_::_in %n months_" : ["em %n mês","em %n meses"], + "_%n month ago_::_%n months ago_" : ["há %n mês atrás","há %n meses"], + "next year" : "ano que vem", + "last year" : "último ano", + "_in %n year_::_in %n years_" : ["em %n ano","em %n anos"], + "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], + "_in %n hour_::_in %n hours_" : ["em %n hora","em %n horas"], + "_%n hour ago_::_%n hours ago_" : ["há %n hora atrás","há %n horas"], + "_in %n minute_::_in %n minutes_" : ["em %n minuto","em %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["há %n minuto atrás","há %n minutos"], + "in a few seconds" : "Em alguns segundos", + "seconds ago" : "segundos atrás", + "Empty file" : "Arquivo vazio", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "O módulo com a ID: %s não existe. Por favor, habilite-o nas configurações de seu aplicativo ou contacte o administrador.", + "File name is a reserved word" : "O nome do arquivo é uma palavra reservada", + "File name contains at least one invalid character" : "O nome do arquivo contém pelo menos um caracter inválido", + "File name is too long" : "O nome do arquivo é muito longo", + "Dot files are not allowed" : "Arquivos Dot não são permitidos", + "Empty filename is not allowed" : "Nome vazio para arquivo não é permitido.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "O aplicativo \"%s\" não pode ser instalado pois o arquivo appinfo não pôde ser lido.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "O aplicativo \"%s\" não pode ser instalado pois não é compatível com a versão do servidor.", + "__language_name__" : "Português Brasileiro", + "This is an automatically sent email, please do not reply." : "Este e-mail é enviado automaticamente. Por favor, não responda.", + "Help" : "Ajuda", + "Apps" : "Aplicativos", + "Settings" : "Configurações", + "Log out" : "Sair", + "Users" : "Usuários", + "Unknown user" : "Usuário desconhecido", + "Additional settings" : "Configurações adicionais", + "%s enter the database username and name." : "%s insira o nome de usuário e o nome do banco de dados.", + "%s enter the database username." : "%s insira o nome de usuário do banco de dados.", + "%s enter the database name." : "%s insira o nome do banco de dados.", + "%s you may not use dots in the database name" : "%s você não pode usar pontos no nome do banco de dados", + "MySQL username and/or password not valid" : "Nome de usuário e/ou senha do MySQL inválidos", + "You need to enter details of an existing account." : "Você necessita entrar detalhes de uma conta existente.", + "Oracle connection could not be established" : "Conexão Oracle não pôde ser estabelecida", + "Oracle username and/or password not valid" : "Nome de usuário e/ou senha Oracle inválidos", + "PostgreSQL username and/or password not valid" : "Nome de usuário e/ou senha PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X não é suportado e %s não funcionará corretamente nesta plataforma. Use-o por sua conta e risco!", + "For the best results, please consider using a GNU/Linux server instead." : "Para obter melhores resultados, por favor considere o uso de um servidor GNU/Linux em seu lugar.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Aparentemente a instância %s está rodando em um ambiente PHP de 32 bits e o open_basedir foi configurado no php.ini. Isto pode gerar problemas com arquivos maiores que 4GB e é altamente não recomendável.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, remova a configuração de open_basedir de seu php.ini ou mude o PHP para 64bit.", + "Set an admin username." : "Defina um nome do usuário administrador.", + "Set an admin password." : "Defina uma senha para o administrador.", + "Can't create or write into the data directory %s" : "Não foi possível criar ou gravar no diretório de dados %s", + "Invalid Federated Cloud ID" : "ID inválida de Nuvem Federada", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "A plataforma de compartilhamento %s deve implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Plataforma de serviço de compartilhamento %s não encontrada", + "Sharing backend for %s not found" : "Plataforma de compartilhamento para %s não foi encontrada", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartilhou »%2$s« com você e quer adicionar:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s compartilhou »%2$s« com você e quer adicionar", + "»%s« added a note to a file shared with you" : "»%s« adicionou uma anotação num arquivo compartilhado com você", + "Open »%s«" : "Abrir »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Você não tem permissão para compartilhar %s", + "Can’t increase permissions of %s" : "Não posso aumentar as permissões de %s", + "Files can’t be shared with delete permissions" : "Os arquivos não podem ser compartilhados com permissões de exclusão", + "Files can’t be shared with create permissions" : "Os arquivos não podem ser compartilhados com permissões de criação", + "Expiration date is in the past" : "Data de expiração está no passado", + "Can’t set expiration date more than %s days in the future" : "Não é possível definir a expiração mais do que %s dias no futuro", + "%1$s shared »%2$s« with you" : "%1$s compartilhou »%2$s« com você", + "%1$s shared »%2$s« with you." : "%1$s compartilhou »%2$s« com você.", + "Click the button below to open it." : "Clique no botão abaixo para abri-lo.", + "The requested share does not exist anymore" : "O compartilhamento solicitado não existe mais", + "Could not find category \"%s\"" : "Impossível localizar a categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Segunda-feira", + "Tuesday" : "Terça-feira", + "Wednesday" : "Quarta-feira", + "Thursday" : "Quinta-feira", + "Friday" : "Sexta-feira", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Seg.", + "Tue." : "Ter.", + "Wed." : "Qua.", + "Thu." : "Qui.", + "Fri." : "Sex.", + "Sat." : "Sab.", + "Su" : "Su", + "Mo" : "Se", + "Tu" : "Te", + "We" : "Qu", + "Th" : "Qu", + "Fr" : "Se", + "Sa" : "Sa", + "January" : "Janeiro", + "February" : "Fevereiro", + "March" : "Março", + "April" : "Abril", + "May" : "Maio", + "June" : "Junho", + "July" : "Julho", + "August" : "Agosto", + "September" : "Setembro", + "October" : "Outubro", + "November" : "Novembro", + "December" : "Dezembro", + "Jan." : "Jan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Out.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Um nome de usuário válido deve ser fornecido", + "Username contains whitespace at the beginning or at the end" : "O nome de usuário contém espaço em branco no início ou no fim", + "Username must not consist of dots only" : "Nome do usuário não pode consistir de pontos somente", + "Username is invalid because files already exist for this user" : "O nome de usuário é inválido porque já exstem arquivos para este usuário", + "A valid password must be provided" : "Uma senha válida deve ser fornecida", + "The username is already being used" : "Este nome de usuário já está em uso", + "Could not create user" : "Não foi possível criar o usuário", + "User disabled" : "Usuário desativado", + "Login canceled by app" : "Login cancelado pelo aplicativo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "O aplicativo \"%1$s\" não pode ser instalado devido à estas dependências: %2$s", + "a safe home for all your data" : "Um lar seguro para todos os seus dados", + "File is currently busy, please try again later" : "O arquivo está ocupado, tente novamente mais tarde", + "Can't read file" : "Não foi possível ler arquivo", + "Application is not enabled" : "O aplicativo não está habilitado", + "Authentication error" : "Erro de autenticação", + "Token expired. Please reload page." : "O token expirou. Por favor recarregue a página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nenhum driver de banco de dados (sqlite, mysql ou postgresql) instalado.", + "Cannot write into \"config\" directory" : "Não foi possível gravar no diretório \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escritura no diretório config. Veja %s", + "Cannot write into \"apps\" directory" : "Não foi possível gravar no diretório \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Geralmente, isso pode ser corrigido concedendo ao servidor web acesso de gravação ao diretório de aplicativos ou desativando a loja de aplicativos no arquivo de configuração.", + "Cannot create \"data\" directory" : "Não foi possível criar o diretório \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escrita no diretório raiz. Veja %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "As permissões normalmente podem ser corrigidas dando permissão de escrita do diretório raiz para o servidor web. Veja %s.", + "Setting locale to %s failed" : "Falha ao configurar localização para %s", + "Please install one of these locales on your system and restart your webserver." : "Por favor, defina uma dessas localizações em seu sistema e reinicie o seu servidor web.", + "PHP module %s not installed." : "Módulo PHP %s não instalado.", + "Please ask your server administrator to install the module." : "Por favor, peça ao seu administrador do servidor para instalar o módulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "Configuração PHP \"%s\" não está configurado para \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar a configuração no php.ini fará com que o Nextcloud execute novamente", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está definido para \"%s\" ao invés do valor esperado \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corrigir esse problema defina mbstring.func_overload para 0 em seu php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "A libxml2 2.7.0 é a versão mínima requerida. Atualmente a versão %s está instalada.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema, atualize a versão da sua libxml2 e reinicie seu servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP aparentemente está configurado para retirar blocos doc inline. Isso fará com que vários aplicativos do núcleo fiquem inacessíveis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isso provavelmente é causado por um cache/acelerador, como Zend OPcache ou eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Módulos do PHP foram instalados, mas eles ainda estão listados como faltantes?", + "Please ask your server administrator to restart the web server." : "Por favor peça ao administrador do servidor para reiniciar o servidor web.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 requirido", + "Please upgrade your database version" : "Por favor atualize sua versão do banco de dados", + "Your data directory is readable by other users" : "O diretório de dados está legível para outros usuários", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor altere as permissões para 0770 para que o diretório não possa ser lido por outros usuários.", + "Your data directory must be an absolute path" : "O diretório de dados deve ser um caminho absoluto", + "Check the value of \"datadirectory\" in your configuration" : "Verifique o valor do \"datadirectory\" na sua configuração", + "Your data directory is invalid" : "Seu diretório de dados é inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Assegure-se que exista um arquivo chamado \".ocdata\" na raiz do diretório \"data\".", + "Action \"%s\" not supported or implemented." : "Ação \"%s\" não suportada ou implementada.", + "Authentication failed, wrong token or provider ID given" : "Falha na autenticação, token ou ID do provedor errados", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parâmetros ausentes para concluir a solicitação: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "A ID \"%1$s\" já é usada pelo provedor de nuvem federada \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Provedor da Federação de Nuvem com ID: \"%s\" não existe.", + "Could not obtain lock type %d on \"%s\"." : "Não foi possível obter tipo de bloqueio %d em \"%s\".", + "Storage unauthorized. %s" : "Armazenamento não autorizado. %s", + "Storage incomplete configuration. %s" : "Configuração incompleta do armazenamento. %s", + "Storage connection error. %s" : "Erro na conexão de armazenamento. %s", + "Storage is temporarily not available" : "Armazenamento temporariamente indisponível", + "Storage connection timeout. %s" : "Atingido o tempo limite de conexão ao armazenamento. %s", + "Following databases are supported: %s" : "Os seguintes bancos de dados são suportados: %s", + "Following platforms are supported: %s" : "As seguintes plataformas são suportadas: %s", + "Overview" : "Visão geral", + "Basic settings" : "Configurações básicas", + "Sharing" : "Compartilhamento", + "Security" : "Segurança", + "Groupware" : "Groupware", + "Personal info" : "Informação pessoal", + "Mobile & desktop" : "Móvel & desktop", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Normalmente isso pode ser resolvido dando ao webserver permissão de escrita no diretório apps ou desabilitando a appstore no arquivo de configuração. Veja %s" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/pt_PT.js b/docker/overlays/nextcloud/html/lib/l10n/pt_PT.js new file mode 100644 index 0000000..0096765 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pt_PT.js @@ -0,0 +1,203 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Não é possível gravar no directório \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Isto normalmente pode ser resolvido, dando ao servidor da Web direitos de gravação para a diretoria de configuração", + "See %s" : "Ver %s", + "Sample configuration detected" : "Detetado exemplo de configuração", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de amostra foi copiada. Isso pode danificar a sua instalação e não é suportado. Por favor, leia a documentação antes de realizar mudanças no config.php", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edição Educação", + "Enterprise bundle" : "Pacote Empresa", + "Groupware bundle" : "Pacote Colaborativo", + "Social sharing bundle" : "Pacote Partilha Social", + "PHP %s or higher is required." : "Necessário PHP %s ou superior.", + "PHP with a version lower than %s is required." : "É necessário um PHP com uma versão inferir a %s.", + "%sbit or higher PHP required." : "Necessário PHP %sbit ou superior.", + "The command line tool %s could not be found" : "Não foi encontrada a ferramenta de linha de comando %s", + "The library %s is not available." : "A biblioteca %s não está disponível.", + "Server version %s or higher is required." : "É necessária versão do servidor %s or superior. ", + "Server version %s or lower is required." : "É necessária versão do servidor %s or inferior.", + "Authentication" : "Autenticação", + "Unknown filetype" : "Tipo de ficheiro desconhecido", + "Invalid image" : "Imagem inválida", + "Avatar image is not square" : "A imagem do avatar não é quadrada.", + "today" : "hoje", + "tomorrow" : "Amanhã", + "yesterday" : "ontem", + "_in %n day_::_in %n days_" : ["em %n dia","em %n dias"], + "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], + "next month" : "Próximo mês", + "last month" : "ultimo mês", + "_in %n month_::_in %n months_" : ["em %n mês","em %n meses"], + "_%n month ago_::_%n months ago_" : ["%n mês atrás","%n meses atrás"], + "next year" : "Próximo ano", + "last year" : "ano passado", + "_in %n year_::_in %n years_" : ["dentro de%n ano","dentro de%n anos"], + "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], + "_in %n hour_::_in %n hours_" : ["dentro de %n hora","dentro de %n horas"], + "_%n hour ago_::_%n hours ago_" : ["%n hora atrás","%n horas atrás"], + "_in %n minute_::_in %n minutes_" : ["dentro de %n minuto","dentro de %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuto atrás","%n minutos atrás"], + "in a few seconds" : "em breves segundos", + "seconds ago" : "Minutos atrás", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Módulo com ID: %s não existe. Por favor active-o nas definições da aplicação ou contacte o administrador.", + "File name is a reserved word" : "Nome de ficheiro é uma palavra reservada", + "File name contains at least one invalid character" : "Nome de ficheiro contém pelo menos um caráter inválido", + "File name is too long" : "Nome do ficheiro demasiado longo", + "Dot files are not allowed" : "Ficheiros dot não são permitidos", + "Empty filename is not allowed" : "Não é permitido um ficheiro sem nome", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "A app \"%s\" não pode ser instalada porque o ficheiro appinfo não pode ser lido.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "A aplicação \"%s\" não pode ser instada porque não é compatível com esta versão do servidor.", + "__language_name__" : "Português", + "This is an automatically sent email, please do not reply." : "Este e-mail foi enviado automaticamente, por favor não responda a este e-mail.", + "Help" : "Ajuda", + "Apps" : "Apps", + "Settings" : "Definições", + "Log out" : "Sair", + "Users" : "Utilizadores", + "Unknown user" : "Utilizador desconhecido", + "Additional settings" : "Definições adicionais", + "%s enter the database username and name." : "%s introduza o nome de utilizador da base de dados e o nome da base de dados.", + "%s enter the database username." : "%s introduza o nome de utilizador da base de dados", + "%s enter the database name." : "%s introduza o nome da base de dados", + "%s you may not use dots in the database name" : "%s não é permitido utilizar pontos (.) no nome da base de dados", + "You need to enter details of an existing account." : "Precisa de introduzir detalhes de uma conta existente.", + "Oracle connection could not be established" : "Não foi possível estabelecer a ligação Oracle", + "Oracle username and/or password not valid" : "Nome de utilizador/palavra-passe do Oracle inválidos", + "PostgreSQL username and/or password not valid" : "Nome de utilizador/password do PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Esta plataforma não suporta o sistema operativo Mac OS X e o %s poderá não funcionar correctamente. Utilize por sua conta e risco.", + "For the best results, please consider using a GNU/Linux server instead." : "Para um melhor resultado, utilize antes o servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Parece que a instância %s está a ser executada num ambiente PHP de 32-bits e o open_basedir foi configurado no php.ini. Isto levará a problemas com ficheiros de tamanho superior a 4 GB e é altamente desencorajado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, remova a definição open_basedir do seu php.ini ou altere o seu PHP para 64-bits.", + "Set an admin username." : "Definir um nome de utilizador de administrador", + "Set an admin password." : "Definia uma palavra-passe de administrador.", + "Can't create or write into the data directory %s" : "Não é possível criar ou escrever a directoria data %s", + "Invalid Federated Cloud ID" : "Id. de Nuvem Federada Inválida", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Ao partilhar a interface %s deve implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Não foi encontrada a partilha da interface %s", + "Sharing backend for %s not found" : "Não foi encontrada a partilha da interface para %s", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s partilhado »%2$s« consigo e quer adicionar:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s partilhado »%2$s« consigo e quer adicionar:", + "»%s« added a note to a file shared with you" : "»%s« adicionou uma nota a um ficheiro partilhado consigo", + "Open »%s«" : "Abrir »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Não está autorizado a partilhar %s", + "Can’t increase permissions of %s" : "Não é possível aumentar as permissões de %s", + "Files can’t be shared with delete permissions" : "Ficheiros não podem ser partilhados com permissões de apagar", + "Files can’t be shared with create permissions" : "Ficheiros não podem ser partilhados com permissões de criação", + "Expiration date is in the past" : "A data de expiração está no passado", + "Can’t set expiration date more than %s days in the future" : "Não é possível definir data de expiração a mais de %s dias no futuro", + "%1$s shared »%2$s« with you" : "%1$s partilhado »%2$s« contigo", + "%1$s shared »%2$s« with you." : "%1$s partilhado »%2$s« contigo.", + "Click the button below to open it." : "Clicar no botão abaixo para abrir.", + "The requested share does not exist anymore" : "A partilha requisitada já não existe", + "Could not find category \"%s\"" : "Não foi encontrado a categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Segunda-feira", + "Tuesday" : "Terça-feira", + "Wednesday" : "Quarta-feira", + "Thursday" : "Quinta-feira", + "Friday" : "Sexta-feira", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Seg.", + "Tue." : "Ter.", + "Wed." : "Qua.", + "Thu." : "Qui.", + "Fri." : "Sex.", + "Sat." : "Sáb.", + "Su" : "Dom", + "Mo" : "Seg", + "Tu" : "Ter", + "We" : "Qua", + "Th" : "Qui", + "Fr" : "Sex", + "Sa" : "Sáb", + "January" : "Janeiro", + "February" : "Fevereiro", + "March" : "Março", + "April" : "Abril", + "May" : "Maio", + "June" : "Junho", + "July" : "Julho", + "August" : "Agosto", + "September" : "Setembro", + "October" : "Outubro", + "November" : "Novembro", + "December" : "Dezembro", + "Jan." : "Jan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Out.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Apenas os seguintes caracteres são permitidos num nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Um nome de utilizador válido deve ser fornecido", + "Username contains whitespace at the beginning or at the end" : "Nome de utilizador contém espaço em branco no início ou no fim", + "Username must not consist of dots only" : "O utilizador não pode consistir de apenas pontos", + "A valid password must be provided" : "Deve ser fornecida uma palavra-passe válida", + "The username is already being used" : "O nome de utilizador já está a ser usado", + "Could not create user" : "Não foi possível criar o utilizador", + "User disabled" : "Utilizador desativado", + "Login canceled by app" : "Sessão cancelada pela app", + "a safe home for all your data" : "Um lugar seguro para todos os seus dados", + "File is currently busy, please try again later" : "O ficheiro está ocupado, por favor, tente mais tarde", + "Can't read file" : "Não é possível ler o ficheiro", + "Application is not enabled" : "A aplicação não está activada", + "Authentication error" : "Erro na autenticação", + "Token expired. Please reload page." : "O token expirou. Por favor recarregue a página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nenhuma base de dados de drivers (sqlite, mysql, or postgresql) instaladas.", + "Cannot write into \"config\" directory" : "Não é possível escrever na directoria \"configurar\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Isto pode geralmente ser corrigido ao adicionar permissões de escrita à pasta de configuração ao servidor web. Ver %s.", + "Cannot write into \"apps\" directory" : "Não é possivel escrever na directoria \"aplicações\"", + "Cannot create \"data\" directory" : "Não é possivel criar a directoria \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Isto pode geralmente ser corrigido ao adicionar permissões de escrita à pasta de raiz. Ver %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "As permissões podem geralmente ser corrigidas dando ao servidor web permissões de escrita na pasta de raiz. Ver %s.", + "Setting locale to %s failed" : "Definindo local para %s falhado", + "Please install one of these locales on your system and restart your webserver." : "Por favor instale um destes locais no seu sistema e reinicie o seu servidor web.", + "PHP module %s not installed." : "O modulo %s PHP não está instalado.", + "Please ask your server administrator to install the module." : "Por favor pergunte ao seu administrador do servidor para instalar o modulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "Configuração PHP \"%s\" não está definida para \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta definição no php.ini fará com que o Nextcloud inicie novamente", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está configurado para \"%s\" invés do valor habitual de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corrigir este problema altere o mbstring.func_overload para 0 no seu php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Necessária pelo menos libxml2 2.7.0. Atualmente %s está instalada.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema actualize a versão da libxml2 e reinicie o seu servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado a remover blocos doc em linha. Isto vai tornar algumas aplicações básicas inacessíveis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto é provavelmente causado por uma cache/acelerador como o Zend OPcache or eAcelerador.", + "PHP modules have been installed, but they are still listed as missing?" : "Os módulos PHP foram instalados, mas eles ainda estão listados como desaparecidos?", + "Please ask your server administrator to restart the web server." : "Pro favor pergunte ao seu administrador do servidor para reiniciar o servidor da internet.", + "PostgreSQL >= 9 required" : "Necessita PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualize a sua versão da base de dados", + "Your data directory is readable by other users" : "O seu directório de dados é legível por outros utilizadores", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor altere as permissões para 0770 para que esse directório não possa ser listado por outros utilizadores.", + "Your data directory must be an absolute path" : "O seu directório de dados deve ser um caminho absoluto", + "Check the value of \"datadirectory\" in your configuration" : "Verifique o valor de \"datadirectory\" na sua configuração", + "Your data directory is invalid" : "O seu directório de dados é inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Garanta que existe um ficheiro chamado \".occdata\" na raiz do directório de dados", + "Could not obtain lock type %d on \"%s\"." : "Não foi possível obter o tipo de bloqueio %d em \"%s\".", + "Storage unauthorized. %s" : "Armazenamento desautorizado. %s", + "Storage incomplete configuration. %s" : "Configuração incompleta do armazenamento. %s", + "Storage connection error. %s" : "Erro de ligação ao armazenamento. %s", + "Storage is temporarily not available" : "Armazenamento temporariamente indisponível", + "Storage connection timeout. %s" : "Tempo de ligação ao armazenamento expirou. %s", + "Following databases are supported: %s" : "São suportadas as seguintes bases de dados: %s", + "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", + "Overview" : "Visão Geral", + "Basic settings" : "Definições básicas", + "Sharing" : "Partilhar", + "Security" : "Segurança", + "Personal info" : "Informação pessoal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Isto pode ser normalmente resolvido dando ao servidor web direito de escrita para o directório de aplicação ou desactivando a loja de aplicações no ficheiro de configuração. Ver %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/pt_PT.json b/docker/overlays/nextcloud/html/lib/l10n/pt_PT.json new file mode 100644 index 0000000..1500a0a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/pt_PT.json @@ -0,0 +1,201 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Não é possível gravar no directório \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Isto normalmente pode ser resolvido, dando ao servidor da Web direitos de gravação para a diretoria de configuração", + "See %s" : "Ver %s", + "Sample configuration detected" : "Detetado exemplo de configuração", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de amostra foi copiada. Isso pode danificar a sua instalação e não é suportado. Por favor, leia a documentação antes de realizar mudanças no config.php", + "%1$s and %2$s" : "%1$s e %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s e %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s e %5$s", + "Education Edition" : "Edição Educação", + "Enterprise bundle" : "Pacote Empresa", + "Groupware bundle" : "Pacote Colaborativo", + "Social sharing bundle" : "Pacote Partilha Social", + "PHP %s or higher is required." : "Necessário PHP %s ou superior.", + "PHP with a version lower than %s is required." : "É necessário um PHP com uma versão inferir a %s.", + "%sbit or higher PHP required." : "Necessário PHP %sbit ou superior.", + "The command line tool %s could not be found" : "Não foi encontrada a ferramenta de linha de comando %s", + "The library %s is not available." : "A biblioteca %s não está disponível.", + "Server version %s or higher is required." : "É necessária versão do servidor %s or superior. ", + "Server version %s or lower is required." : "É necessária versão do servidor %s or inferior.", + "Authentication" : "Autenticação", + "Unknown filetype" : "Tipo de ficheiro desconhecido", + "Invalid image" : "Imagem inválida", + "Avatar image is not square" : "A imagem do avatar não é quadrada.", + "today" : "hoje", + "tomorrow" : "Amanhã", + "yesterday" : "ontem", + "_in %n day_::_in %n days_" : ["em %n dia","em %n dias"], + "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], + "next month" : "Próximo mês", + "last month" : "ultimo mês", + "_in %n month_::_in %n months_" : ["em %n mês","em %n meses"], + "_%n month ago_::_%n months ago_" : ["%n mês atrás","%n meses atrás"], + "next year" : "Próximo ano", + "last year" : "ano passado", + "_in %n year_::_in %n years_" : ["dentro de%n ano","dentro de%n anos"], + "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], + "_in %n hour_::_in %n hours_" : ["dentro de %n hora","dentro de %n horas"], + "_%n hour ago_::_%n hours ago_" : ["%n hora atrás","%n horas atrás"], + "_in %n minute_::_in %n minutes_" : ["dentro de %n minuto","dentro de %n minutos"], + "_%n minute ago_::_%n minutes ago_" : ["%n minuto atrás","%n minutos atrás"], + "in a few seconds" : "em breves segundos", + "seconds ago" : "Minutos atrás", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Módulo com ID: %s não existe. Por favor active-o nas definições da aplicação ou contacte o administrador.", + "File name is a reserved word" : "Nome de ficheiro é uma palavra reservada", + "File name contains at least one invalid character" : "Nome de ficheiro contém pelo menos um caráter inválido", + "File name is too long" : "Nome do ficheiro demasiado longo", + "Dot files are not allowed" : "Ficheiros dot não são permitidos", + "Empty filename is not allowed" : "Não é permitido um ficheiro sem nome", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "A app \"%s\" não pode ser instalada porque o ficheiro appinfo não pode ser lido.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "A aplicação \"%s\" não pode ser instada porque não é compatível com esta versão do servidor.", + "__language_name__" : "Português", + "This is an automatically sent email, please do not reply." : "Este e-mail foi enviado automaticamente, por favor não responda a este e-mail.", + "Help" : "Ajuda", + "Apps" : "Apps", + "Settings" : "Definições", + "Log out" : "Sair", + "Users" : "Utilizadores", + "Unknown user" : "Utilizador desconhecido", + "Additional settings" : "Definições adicionais", + "%s enter the database username and name." : "%s introduza o nome de utilizador da base de dados e o nome da base de dados.", + "%s enter the database username." : "%s introduza o nome de utilizador da base de dados", + "%s enter the database name." : "%s introduza o nome da base de dados", + "%s you may not use dots in the database name" : "%s não é permitido utilizar pontos (.) no nome da base de dados", + "You need to enter details of an existing account." : "Precisa de introduzir detalhes de uma conta existente.", + "Oracle connection could not be established" : "Não foi possível estabelecer a ligação Oracle", + "Oracle username and/or password not valid" : "Nome de utilizador/palavra-passe do Oracle inválidos", + "PostgreSQL username and/or password not valid" : "Nome de utilizador/password do PostgreSQL inválidos", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Esta plataforma não suporta o sistema operativo Mac OS X e o %s poderá não funcionar correctamente. Utilize por sua conta e risco.", + "For the best results, please consider using a GNU/Linux server instead." : "Para um melhor resultado, utilize antes o servidor GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Parece que a instância %s está a ser executada num ambiente PHP de 32-bits e o open_basedir foi configurado no php.ini. Isto levará a problemas com ficheiros de tamanho superior a 4 GB e é altamente desencorajado.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, remova a definição open_basedir do seu php.ini ou altere o seu PHP para 64-bits.", + "Set an admin username." : "Definir um nome de utilizador de administrador", + "Set an admin password." : "Definia uma palavra-passe de administrador.", + "Can't create or write into the data directory %s" : "Não é possível criar ou escrever a directoria data %s", + "Invalid Federated Cloud ID" : "Id. de Nuvem Federada Inválida", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Ao partilhar a interface %s deve implementar a interface OCP\\Share_Backend", + "Sharing backend %s not found" : "Não foi encontrada a partilha da interface %s", + "Sharing backend for %s not found" : "Não foi encontrada a partilha da interface para %s", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s partilhado »%2$s« consigo e quer adicionar:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s partilhado »%2$s« consigo e quer adicionar:", + "»%s« added a note to a file shared with you" : "»%s« adicionou uma nota a um ficheiro partilhado consigo", + "Open »%s«" : "Abrir »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Não está autorizado a partilhar %s", + "Can’t increase permissions of %s" : "Não é possível aumentar as permissões de %s", + "Files can’t be shared with delete permissions" : "Ficheiros não podem ser partilhados com permissões de apagar", + "Files can’t be shared with create permissions" : "Ficheiros não podem ser partilhados com permissões de criação", + "Expiration date is in the past" : "A data de expiração está no passado", + "Can’t set expiration date more than %s days in the future" : "Não é possível definir data de expiração a mais de %s dias no futuro", + "%1$s shared »%2$s« with you" : "%1$s partilhado »%2$s« contigo", + "%1$s shared »%2$s« with you." : "%1$s partilhado »%2$s« contigo.", + "Click the button below to open it." : "Clicar no botão abaixo para abrir.", + "The requested share does not exist anymore" : "A partilha requisitada já não existe", + "Could not find category \"%s\"" : "Não foi encontrado a categoria \"%s\"", + "Sunday" : "Domingo", + "Monday" : "Segunda-feira", + "Tuesday" : "Terça-feira", + "Wednesday" : "Quarta-feira", + "Thursday" : "Quinta-feira", + "Friday" : "Sexta-feira", + "Saturday" : "Sábado", + "Sun." : "Dom.", + "Mon." : "Seg.", + "Tue." : "Ter.", + "Wed." : "Qua.", + "Thu." : "Qui.", + "Fri." : "Sex.", + "Sat." : "Sáb.", + "Su" : "Dom", + "Mo" : "Seg", + "Tu" : "Ter", + "We" : "Qua", + "Th" : "Qui", + "Fr" : "Sex", + "Sa" : "Sáb", + "January" : "Janeiro", + "February" : "Fevereiro", + "March" : "Março", + "April" : "Abril", + "May" : "Maio", + "June" : "Junho", + "July" : "Julho", + "August" : "Agosto", + "September" : "Setembro", + "October" : "Outubro", + "November" : "Novembro", + "December" : "Dezembro", + "Jan." : "Jan.", + "Feb." : "Fev.", + "Mar." : "Mar.", + "Apr." : "Abr.", + "May." : "Mai.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Ago.", + "Sep." : "Set.", + "Oct." : "Out.", + "Nov." : "Nov.", + "Dec." : "Dez.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Apenas os seguintes caracteres são permitidos num nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"", + "A valid username must be provided" : "Um nome de utilizador válido deve ser fornecido", + "Username contains whitespace at the beginning or at the end" : "Nome de utilizador contém espaço em branco no início ou no fim", + "Username must not consist of dots only" : "O utilizador não pode consistir de apenas pontos", + "A valid password must be provided" : "Deve ser fornecida uma palavra-passe válida", + "The username is already being used" : "O nome de utilizador já está a ser usado", + "Could not create user" : "Não foi possível criar o utilizador", + "User disabled" : "Utilizador desativado", + "Login canceled by app" : "Sessão cancelada pela app", + "a safe home for all your data" : "Um lugar seguro para todos os seus dados", + "File is currently busy, please try again later" : "O ficheiro está ocupado, por favor, tente mais tarde", + "Can't read file" : "Não é possível ler o ficheiro", + "Application is not enabled" : "A aplicação não está activada", + "Authentication error" : "Erro na autenticação", + "Token expired. Please reload page." : "O token expirou. Por favor recarregue a página.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Nenhuma base de dados de drivers (sqlite, mysql, or postgresql) instaladas.", + "Cannot write into \"config\" directory" : "Não é possível escrever na directoria \"configurar\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Isto pode geralmente ser corrigido ao adicionar permissões de escrita à pasta de configuração ao servidor web. Ver %s.", + "Cannot write into \"apps\" directory" : "Não é possivel escrever na directoria \"aplicações\"", + "Cannot create \"data\" directory" : "Não é possivel criar a directoria \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Isto pode geralmente ser corrigido ao adicionar permissões de escrita à pasta de raiz. Ver %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "As permissões podem geralmente ser corrigidas dando ao servidor web permissões de escrita na pasta de raiz. Ver %s.", + "Setting locale to %s failed" : "Definindo local para %s falhado", + "Please install one of these locales on your system and restart your webserver." : "Por favor instale um destes locais no seu sistema e reinicie o seu servidor web.", + "PHP module %s not installed." : "O modulo %s PHP não está instalado.", + "Please ask your server administrator to install the module." : "Por favor pergunte ao seu administrador do servidor para instalar o modulo.", + "PHP setting \"%s\" is not set to \"%s\"." : "Configuração PHP \"%s\" não está definida para \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta definição no php.ini fará com que o Nextcloud inicie novamente", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload está configurado para \"%s\" invés do valor habitual de \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Para corrigir este problema altere o mbstring.func_overload para 0 no seu php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Necessária pelo menos libxml2 2.7.0. Atualmente %s está instalada.", + "To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema actualize a versão da libxml2 e reinicie o seu servidor web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado a remover blocos doc em linha. Isto vai tornar algumas aplicações básicas inacessíveis.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto é provavelmente causado por uma cache/acelerador como o Zend OPcache or eAcelerador.", + "PHP modules have been installed, but they are still listed as missing?" : "Os módulos PHP foram instalados, mas eles ainda estão listados como desaparecidos?", + "Please ask your server administrator to restart the web server." : "Pro favor pergunte ao seu administrador do servidor para reiniciar o servidor da internet.", + "PostgreSQL >= 9 required" : "Necessita PostgreSQL >= 9", + "Please upgrade your database version" : "Por favor actualize a sua versão da base de dados", + "Your data directory is readable by other users" : "O seu directório de dados é legível por outros utilizadores", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor altere as permissões para 0770 para que esse directório não possa ser listado por outros utilizadores.", + "Your data directory must be an absolute path" : "O seu directório de dados deve ser um caminho absoluto", + "Check the value of \"datadirectory\" in your configuration" : "Verifique o valor de \"datadirectory\" na sua configuração", + "Your data directory is invalid" : "O seu directório de dados é inválido", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Garanta que existe um ficheiro chamado \".occdata\" na raiz do directório de dados", + "Could not obtain lock type %d on \"%s\"." : "Não foi possível obter o tipo de bloqueio %d em \"%s\".", + "Storage unauthorized. %s" : "Armazenamento desautorizado. %s", + "Storage incomplete configuration. %s" : "Configuração incompleta do armazenamento. %s", + "Storage connection error. %s" : "Erro de ligação ao armazenamento. %s", + "Storage is temporarily not available" : "Armazenamento temporariamente indisponível", + "Storage connection timeout. %s" : "Tempo de ligação ao armazenamento expirou. %s", + "Following databases are supported: %s" : "São suportadas as seguintes bases de dados: %s", + "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", + "Overview" : "Visão Geral", + "Basic settings" : "Definições básicas", + "Sharing" : "Partilhar", + "Security" : "Segurança", + "Personal info" : "Informação pessoal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Isto pode ser normalmente resolvido dando ao servidor web direito de escrita para o directório de aplicação ou desactivando a loja de aplicações no ficheiro de configuração. Ver %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ro.js b/docker/overlays/nextcloud/html/lib/l10n/ro.js new file mode 100644 index 0000000..bb37060 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ro.js @@ -0,0 +1,136 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nu se poate scrie în folderul \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Aceasta se poate repara de obicei prin permiterea accesului de scriere la dosarul de configurare al serverului Web", + "See %s" : "Vezi %s", + "Sample configuration detected" : "A fost detectată o configurație exemplu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S-a detectat copierea configurației exemplu. Acest lucru poate duce la oprirea instanței tale și nu este suportat. Te rugăm să citești documentația înainte de a face modificări în fișierul config.php", + "%1$s and %2$s" : "%1$s și %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s și %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s și %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s și %5$s", + "PHP %s or higher is required." : "Versiunea PHP %s sau mai mare este necesară.", + "PHP with a version lower than %s is required." : "Este necesară o versiune PHP mai mică decât %s", + "%sbit or higher PHP required." : "Este necesar PHP %sbit sau mai mare.", + "The command line tool %s could not be found" : "Unealta în linie de comandă %s nu a fost găsită", + "The library %s is not available." : "Biblioteca %s nu este disponibilă.", + "Authentication" : "Autentificare", + "Unknown filetype" : "Tip fișier necunoscut", + "Invalid image" : "Imagine invalidă", + "today" : "astăzi", + "tomorrow" : "mâine", + "yesterday" : "ieri", + "_%n day ago_::_%n days ago_" : ["Acum o zi","Acum %n zile","Acum %n zile"], + "next month" : "luna viitoare", + "last month" : "ultima lună", + "_%n month ago_::_%n months ago_" : ["%n lună în urmă","%n luni în urmă","%n luni în urmă"], + "next year" : "anul viitor", + "last year" : "ultimul an", + "_%n year ago_::_%n years ago_" : ["%n an în urmă","%n ani în urmâ","%n ani în urmâ"], + "in a few seconds" : "în câteva secunde", + "seconds ago" : "secunde în urmă", + "File name is a reserved word" : "Numele fișierului este un cuvânt rezervat", + "File name contains at least one invalid character" : "Numele fișierului conține cel puțin un caracter invalid", + "File name is too long" : "Numele fișierului este prea lung", + "Dot files are not allowed" : "Fișierele care încep cu caracterul punct nu sunt permise", + "Empty filename is not allowed" : "Nu este permis fișier fără nume", + "__language_name__" : "Română", + "Help" : "Ajutor", + "Apps" : "Aplicații", + "Settings" : "Setări", + "Log out" : "Ieșire", + "Users" : "Utilizatori", + "Unknown user" : "Utilizator necunoscut", + "Additional settings" : "Setări adiționale", + "%s enter the database username and name." : "%s introdu numele de utilizator și parola pentru baza de date.", + "%s enter the database username." : "%s introdu utilizatorul bazei de date.", + "%s enter the database name." : "%s introduceți numele bazei de date", + "Oracle connection could not be established" : "Conexiunea Oracle nu a putut fi stabilită", + "Oracle username and/or password not valid" : "Numele de utilizator sau / și parola Oracle nu sunt valide", + "PostgreSQL username and/or password not valid" : "Nume utilizator și/sau parolă PostgreSQL greșită", + "For the best results, please consider using a GNU/Linux server instead." : "Pentru cele mai bune rezultate, ia în calcul folosirea unui server care rulează un sistem de operare GNU/Linux.", + "Set an admin username." : "Setează un nume de administrator.", + "Set an admin password." : "Setează o parolă de administrator.", + "Invalid Federated Cloud ID" : "ID invalid cloud federalizat", + "»%s« added a note to a file shared with you" : "%s« a adaugat un comentariu la un fișier partajat cu tine", + "Open »%s«" : "Deschide »%s«", + "%1$s via %2$s" : "%1$sprin %2$s", + "You are not allowed to share %s" : "Nu există permisiunea de partajare %s", + "Files can’t be shared with delete permissions" : "Fișierele nu pot fi partajate cu permisiuni de ștergere", + "Expiration date is in the past" : "Data expirării este în trecut", + "%1$s shared »%2$s« with you." : "%1$sa partajat »%2$s« cu tine.", + "Click the button below to open it." : "Apasă pe butonul de jos pentru a deschide.", + "Could not find category \"%s\"" : "Cloud nu a gasit categoria \"%s\"", + "Sunday" : "Duminică", + "Monday" : "Luni", + "Tuesday" : "Marți", + "Wednesday" : "Miercuri", + "Thursday" : "Joi", + "Friday" : "Vineri", + "Saturday" : "Sâmbătă", + "Sun." : "Dum.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Joi", + "Fri." : "Vin.", + "Sat." : "Sâm.", + "Su" : "Du", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Jo", + "Fr" : "Vi", + "Sa" : "Sâ", + "January" : "Ianuarie", + "February" : "Februarie", + "March" : "Martie", + "April" : "Aprilie", + "May" : "Mai", + "June" : "Iunie", + "July" : "Iulie", + "August" : "August", + "September" : "Septembrie", + "October" : "Octombrie", + "November" : "Noiembrie", + "December" : "Decembrie", + "Jan." : "Ian.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Iun.", + "Jul." : "Iul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Noi.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Numai următoarele caractere sunt permise în numele utilizatorului: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid", + "Username contains whitespace at the beginning or at the end" : "Utilizatorul contine spațiu liber la început sau la sfârșit", + "Username must not consist of dots only" : "Numele utilizatorului nu poate conține numai puncte", + "A valid password must be provided" : "Trebuie să furnizaţi o parolă validă", + "The username is already being used" : "Numele de utilizator este deja folosit", + "Could not create user" : "Nu s-a putut crea utilizatorul", + "User disabled" : "Utilizator dezactivat", + "Application is not enabled" : "Aplicația nu este activată", + "Authentication error" : "Eroare la autentificare", + "Token expired. Please reload page." : "Token expirat. Te rugăm să reîncarci pagina.", + "Cannot write into \"config\" directory" : "Nu se poate scrie în folderul \"config\"", + "Cannot write into \"apps\" directory" : "Nu se poate scrie în folderul \"apps\"", + "PHP module %s not installed." : "Modulul PHP %s nu este instalat.", + "PHP setting \"%s\" is not set to \"%s\"." : "Setarea PHP \"%s\" nu este setată la \"%s\".", + "PHP modules have been installed, but they are still listed as missing?" : "Modulele PHP au fost instalate, dar apar ca lipsind?", + "PostgreSQL >= 9 required" : "Este necesară versiunea 9 sau mai mare a PostgreSQL", + "Please upgrade your database version" : "Actualizați baza de date la o versiune mai nouă", + "Storage is temporarily not available" : "Spațiu de stocare este indisponibil temporar", + "Following databases are supported: %s" : "Următoarele baze de date sunt suportate: %s", + "Following platforms are supported: %s" : "Sunt suportate următoarele platforme: %s", + "Basic settings" : "Setări de bază", + "Sharing" : "Partajare", + "Security" : "Securitate", + "Personal info" : "Informații personale" +}, +"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ro.json b/docker/overlays/nextcloud/html/lib/l10n/ro.json new file mode 100644 index 0000000..601576d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ro.json @@ -0,0 +1,134 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nu se poate scrie în folderul \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Aceasta se poate repara de obicei prin permiterea accesului de scriere la dosarul de configurare al serverului Web", + "See %s" : "Vezi %s", + "Sample configuration detected" : "A fost detectată o configurație exemplu", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S-a detectat copierea configurației exemplu. Acest lucru poate duce la oprirea instanței tale și nu este suportat. Te rugăm să citești documentația înainte de a face modificări în fișierul config.php", + "%1$s and %2$s" : "%1$s și %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s și %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s și %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s și %5$s", + "PHP %s or higher is required." : "Versiunea PHP %s sau mai mare este necesară.", + "PHP with a version lower than %s is required." : "Este necesară o versiune PHP mai mică decât %s", + "%sbit or higher PHP required." : "Este necesar PHP %sbit sau mai mare.", + "The command line tool %s could not be found" : "Unealta în linie de comandă %s nu a fost găsită", + "The library %s is not available." : "Biblioteca %s nu este disponibilă.", + "Authentication" : "Autentificare", + "Unknown filetype" : "Tip fișier necunoscut", + "Invalid image" : "Imagine invalidă", + "today" : "astăzi", + "tomorrow" : "mâine", + "yesterday" : "ieri", + "_%n day ago_::_%n days ago_" : ["Acum o zi","Acum %n zile","Acum %n zile"], + "next month" : "luna viitoare", + "last month" : "ultima lună", + "_%n month ago_::_%n months ago_" : ["%n lună în urmă","%n luni în urmă","%n luni în urmă"], + "next year" : "anul viitor", + "last year" : "ultimul an", + "_%n year ago_::_%n years ago_" : ["%n an în urmă","%n ani în urmâ","%n ani în urmâ"], + "in a few seconds" : "în câteva secunde", + "seconds ago" : "secunde în urmă", + "File name is a reserved word" : "Numele fișierului este un cuvânt rezervat", + "File name contains at least one invalid character" : "Numele fișierului conține cel puțin un caracter invalid", + "File name is too long" : "Numele fișierului este prea lung", + "Dot files are not allowed" : "Fișierele care încep cu caracterul punct nu sunt permise", + "Empty filename is not allowed" : "Nu este permis fișier fără nume", + "__language_name__" : "Română", + "Help" : "Ajutor", + "Apps" : "Aplicații", + "Settings" : "Setări", + "Log out" : "Ieșire", + "Users" : "Utilizatori", + "Unknown user" : "Utilizator necunoscut", + "Additional settings" : "Setări adiționale", + "%s enter the database username and name." : "%s introdu numele de utilizator și parola pentru baza de date.", + "%s enter the database username." : "%s introdu utilizatorul bazei de date.", + "%s enter the database name." : "%s introduceți numele bazei de date", + "Oracle connection could not be established" : "Conexiunea Oracle nu a putut fi stabilită", + "Oracle username and/or password not valid" : "Numele de utilizator sau / și parola Oracle nu sunt valide", + "PostgreSQL username and/or password not valid" : "Nume utilizator și/sau parolă PostgreSQL greșită", + "For the best results, please consider using a GNU/Linux server instead." : "Pentru cele mai bune rezultate, ia în calcul folosirea unui server care rulează un sistem de operare GNU/Linux.", + "Set an admin username." : "Setează un nume de administrator.", + "Set an admin password." : "Setează o parolă de administrator.", + "Invalid Federated Cloud ID" : "ID invalid cloud federalizat", + "»%s« added a note to a file shared with you" : "%s« a adaugat un comentariu la un fișier partajat cu tine", + "Open »%s«" : "Deschide »%s«", + "%1$s via %2$s" : "%1$sprin %2$s", + "You are not allowed to share %s" : "Nu există permisiunea de partajare %s", + "Files can’t be shared with delete permissions" : "Fișierele nu pot fi partajate cu permisiuni de ștergere", + "Expiration date is in the past" : "Data expirării este în trecut", + "%1$s shared »%2$s« with you." : "%1$sa partajat »%2$s« cu tine.", + "Click the button below to open it." : "Apasă pe butonul de jos pentru a deschide.", + "Could not find category \"%s\"" : "Cloud nu a gasit categoria \"%s\"", + "Sunday" : "Duminică", + "Monday" : "Luni", + "Tuesday" : "Marți", + "Wednesday" : "Miercuri", + "Thursday" : "Joi", + "Friday" : "Vineri", + "Saturday" : "Sâmbătă", + "Sun." : "Dum.", + "Mon." : "Lun.", + "Tue." : "Mar.", + "Wed." : "Mie.", + "Thu." : "Joi", + "Fri." : "Vin.", + "Sat." : "Sâm.", + "Su" : "Du", + "Mo" : "Lu", + "Tu" : "Ma", + "We" : "Mi", + "Th" : "Jo", + "Fr" : "Vi", + "Sa" : "Sâ", + "January" : "Ianuarie", + "February" : "Februarie", + "March" : "Martie", + "April" : "Aprilie", + "May" : "Mai", + "June" : "Iunie", + "July" : "Iulie", + "August" : "August", + "September" : "Septembrie", + "October" : "Octombrie", + "November" : "Noiembrie", + "December" : "Decembrie", + "Jan." : "Ian.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Mai", + "Jun." : "Iun.", + "Jul." : "Iul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Oct.", + "Nov." : "Noi.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Numai următoarele caractere sunt permise în numele utilizatorului: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid", + "Username contains whitespace at the beginning or at the end" : "Utilizatorul contine spațiu liber la început sau la sfârșit", + "Username must not consist of dots only" : "Numele utilizatorului nu poate conține numai puncte", + "A valid password must be provided" : "Trebuie să furnizaţi o parolă validă", + "The username is already being used" : "Numele de utilizator este deja folosit", + "Could not create user" : "Nu s-a putut crea utilizatorul", + "User disabled" : "Utilizator dezactivat", + "Application is not enabled" : "Aplicația nu este activată", + "Authentication error" : "Eroare la autentificare", + "Token expired. Please reload page." : "Token expirat. Te rugăm să reîncarci pagina.", + "Cannot write into \"config\" directory" : "Nu se poate scrie în folderul \"config\"", + "Cannot write into \"apps\" directory" : "Nu se poate scrie în folderul \"apps\"", + "PHP module %s not installed." : "Modulul PHP %s nu este instalat.", + "PHP setting \"%s\" is not set to \"%s\"." : "Setarea PHP \"%s\" nu este setată la \"%s\".", + "PHP modules have been installed, but they are still listed as missing?" : "Modulele PHP au fost instalate, dar apar ca lipsind?", + "PostgreSQL >= 9 required" : "Este necesară versiunea 9 sau mai mare a PostgreSQL", + "Please upgrade your database version" : "Actualizați baza de date la o versiune mai nouă", + "Storage is temporarily not available" : "Spațiu de stocare este indisponibil temporar", + "Following databases are supported: %s" : "Următoarele baze de date sunt suportate: %s", + "Following platforms are supported: %s" : "Sunt suportate următoarele platforme: %s", + "Basic settings" : "Setări de bază", + "Sharing" : "Partajare", + "Security" : "Securitate", + "Personal info" : "Informații personale" +},"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ru.js b/docker/overlays/nextcloud/html/lib/l10n/ru.js new file mode 100644 index 0000000..7d74678 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ru.js @@ -0,0 +1,239 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Невозможно выполнить запись в каталог «config».", + "This can usually be fixed by giving the webserver write access to the config directory" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Если для файла «config.php» должен быть установлен режим «только чтение», задайте параметру \"config_is_read_only\" значение \"true\".", + "See %s" : "Обратитесь к %s.", + "This can usually be fixed by giving the webserver write access to the config directory." : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Если для файла «config.php» должен быть установлен режим «только чтение», задайте параметру \"config_is_read_only\" значение \"true\". Изучите %s.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файлы приложения %1$s не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.", + "Sample configuration detected" : "Обнаружена конфигурация из примера.", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Была обнаружена конфигурация из примера. Такая конфигурация не поддерживается и может повредить вашей системе. Прочтите документацию перед внесением изменений в файл config.php", + "Other activities" : "Другие события", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Набор приложений для образовательных учреждений", + "Enterprise bundle" : "Набор приложений для предприятий", + "Groupware bundle" : "Набор приложений для совместной работы", + "Hub bundle" : "Основной набор приложений", + "Social sharing bundle" : "Набор приложений для публикации с использованием соц. сетей", + "PHP %s or higher is required." : "Требуется PHP версии %s или выше.", + "PHP with a version lower than %s is required." : "Требуется PHP версии ниже %s.", + "%sbit or higher PHP required." : "Требуется PHP с разрядностью %s бит или более.", + "The following databases are supported: %s" : "Поддерживаются следующие базы данных: %s", + "The command line tool %s could not be found" : "Утилита командной строки %s не найдена", + "The library %s is not available." : "Библиотека «%s» недоступна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Требуется библиотека «%1$s» версии не меньше %2$s, установлена версия %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Требуется библиотека «%1$s» версии не выше %2$s, установлена версия %3$s.", + "The following platforms are supported: %s" : "Поддерживаются следующие платформы: %s", + "Server version %s or higher is required." : "Требуется сервер версии %s или выше.", + "Server version %s or lower is required." : "Требуется сервер версии %s или ниже.", + "Logged in user must be an admin or sub admin" : "Вошедший в систему пользователь должен обладать правами администратора или суб-администратора", + "Logged in user must be an admin" : "Вошедший в систему пользователь должен обладать правами администратора", + "Wiping of device %s has started" : "Удаление данных с устройства «%s».", + "Wiping of device »%s« has started" : "Удаление данных с устройства «%s».", + "»%s« started remote wipe" : "Начало удаления данных с устройства «%s»", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "На устройстве или в приложении «%s» был начат процесс удаления данных. После его завершения будет отправлено ещё одно электронное письмо.", + "Wiping of device %s has finished" : "Удаление данных с устройства «%s» завершено.", + "Wiping of device »%s« has finished" : "Удаление данных с устройства «%s» завершено.", + "»%s« finished remote wipe" : "Удаление данных с устройства «%s» завершено", + "Device or application »%s« has finished the remote wipe process." : "Процесс удаления данных на устройстве или в приложении «%s» завершён.", + "Remote wipe started" : "Запущен процесс удаления данных с устройства", + "A remote wipe was started on device %s" : "Запущен процесс удаления данных с устройства «%s»", + "Remote wipe finished" : "Процесс удаления данных с устройства завершён", + "The remote wipe on %s has finished" : "Процесс удаления данных с устройства «%s» завершён", + "Authentication" : "Аутентификация", + "Unknown filetype" : "Неизвестный тип файла", + "Invalid image" : "Изображение повреждено", + "Avatar image is not square" : "Изображение аватара не квадратное", + "today" : "сегодня", + "tomorrow" : "завтра", + "yesterday" : "вчера", + "_in %n day_::_in %n days_" : ["через %n день","через %n дня","через %n дней","через %n дня"], + "_%n day ago_::_%n days ago_" : ["%n день назад","%n дня назад","%n дней назад","%n дня назад"], + "next month" : "в следующем месяце", + "last month" : "в прошлом месяце", + "_in %n month_::_in %n months_" : ["через %n месяц","через %n месяца","через %n месяцев","через %n месяца"], + "_%n month ago_::_%n months ago_" : ["%n месяц назад","%n месяца назад","%n месяцев назад","%n месяца назад"], + "next year" : "в следующем году", + "last year" : "в прошлом году", + "_in %n year_::_in %n years_" : ["через %n год","через %n года","через %n лет","через %n года"], + "_%n year ago_::_%n years ago_" : ["%n год назад","%n года назад","%n лет назад","%n года назад"], + "_in %n hour_::_in %n hours_" : ["через %n час","через %n часа","через %n часов","через %n часа"], + "_%n hour ago_::_%n hours ago_" : ["%n час назад","%n часа назад","%n часов назад","%n часа назад"], + "_in %n minute_::_in %n minutes_" : ["через %n минуту","через %n минуты","через %n минут","через %n минуты"], + "_%n minute ago_::_%n minutes ago_" : ["%n минуту назад","%n минуты назад","%n минут назад","%n минуты назад"], + "in a few seconds" : "через несколько секунд", + "seconds ago" : "несколько секунд назад", + "Empty file" : "Пустой файл", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль с ID «%s» не существует. Включите его в настройках приложений или обратитесь к администратору.", + "File name is a reserved word" : "Имя файла является зарезервированным словом", + "File name contains at least one invalid character" : "Имя файла содержит по крайней мере один некорректный символ", + "File name is too long" : "Имя файла слишком длинное.", + "Dot files are not allowed" : "Файлы начинающиеся с точки не допускаются", + "Empty filename is not allowed" : "Пустое имя файла не допускается", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Приложение «%s» не может быть установлено, так как файл с информацией о приложении не может быть прочтен.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Приложение «%s» не может быть установлено, потому что оно несовместимо с этой версией сервера", + "__language_name__" : "Русский", + "This is an automatically sent email, please do not reply." : "Это сообщение отправлено автоматически и не требует ответа.", + "Help" : "Помощь", + "Apps" : "Приложения", + "Settings" : "Настройки", + "Log out" : "Выйти", + "Users" : "Пользователи", + "Unknown user" : "Неизвестный пользователь", + "Additional settings" : "Дополнительные настройки", + "%s enter the database username and name." : "%s укажите имя пользователя и название для базы данных.", + "%s enter the database username." : "%s введите имя пользователя базы данных.", + "%s enter the database name." : "%s введите имя базы данных.", + "%s you may not use dots in the database name" : "%s Вы не можете использовать точки в имени базы данных", + "MySQL username and/or password not valid" : "Неверное имя пользователя и/или пароль для подключения к MySQL", + "You need to enter details of an existing account." : "Необходимо уточнить данные существующего акаунта.", + "Oracle connection could not be established" : "Соединение с Oracle не может быть установлено", + "Oracle username and/or password not valid" : "Неверное имя пользователя и/или пароль Oracle", + "PostgreSQL username and/or password not valid" : "Неверное имя пользователя и/или пароль PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не поддерживается и %s может работать некорректно на данной платформе. Используйте на свой страх и риск!", + "For the best results, please consider using a GNU/Linux server instead." : "Для достижения наилучших результатов, рассмотрите вариант использования сервера на GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Кажется что экземпляр этого %s работает в 32-битной среде PHP и в php.ini был настроен open_basedir. Это приведёт к проблемам с файлами более 4 ГБ и настоятельно не рекомендуется.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Пожалуйста, удалите директиву open_basedir из файла php.ini или смените PHP на 64-разрядную сборку.", + "Set an admin username." : "Задать имя пользователя для администратора.", + "Set an admin password." : "Задать пароль для admin.", + "Can't create or write into the data directory %s" : "Невозможно создать или записать в каталог данных %s", + "Invalid Federated Cloud ID" : "Неверный ID в объединении облачных хранилищ.", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Бэкенд общего доступа %s должен реализовывать интерфейс OCP\\Share_Backend", + "Sharing backend %s not found" : "Механизм предоставления общего доступа %s не найден", + "Sharing backend for %s not found" : "Не найден механизм предоставления общего доступа для %s ", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить", + "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ", + "Open »%s«" : "Открыть «%s»", + "%1$s via %2$s" : "%1$s через %2$s", + "You are not allowed to share %s" : "Вам не разрешено делиться %s", + "Can’t increase permissions of %s" : "Невозможно увеличить права доступа для %s", + "Files can’t be shared with delete permissions" : "Файлы не могут иметь общий доступ с правами на удаление", + "Files can’t be shared with create permissions" : "Файлы не могут иметь общий доступ с правами на создание", + "Expiration date is in the past" : "Дата окончания срока действия уже прошла", + "Can’t set expiration date more than %s days in the future" : "Невозможно установить дату окончания срока действия более %s дней", + "%1$s shared »%2$s« with you" : "%1$s предоставил(а) вам доступ к «%2$s»", + "%1$s shared »%2$s« with you." : "%1$s предоставил(а) вам доступ к «%2$s».", + "Click the button below to open it." : "Нажмите расположенную ниже кнопку для перехода к полученному общему ресурсу.", + "The requested share does not exist anymore" : "Запрошенный общий ресурс более не существует.", + "Could not find category \"%s\"" : "Категория «%s» не найдена", + "Sunday" : "Воскресенье", + "Monday" : "Понедельник", + "Tuesday" : "Вторник", + "Wednesday" : "Среда", + "Thursday" : "Четверг", + "Friday" : "Пятница", + "Saturday" : "Суббота", + "Sun." : "Вс.", + "Mon." : "Пн.", + "Tue." : "Вт.", + "Wed." : "Ср.", + "Thu." : "Чт.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "Su" : "Вс", + "Mo" : "Пн", + "Tu" : "Вт", + "We" : "Ср", + "Th" : "Чт", + "Fr" : "Пт", + "Sa" : "Сб", + "January" : "Январь", + "February" : "Февраль", + "March" : "Март", + "April" : "Апрель", + "May" : "Май", + "June" : "Июнь", + "July" : "Июль", + "August" : "Август", + "September" : "Сентябрь", + "October" : "Октябрь", + "November" : "Ноябрь", + "December" : "Декабрь", + "Jan." : "Янв.", + "Feb." : "Фев.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Май", + "Jun." : "Июн.", + "Jul." : "Июл.", + "Aug." : "Авг.", + "Sep." : "Сен.", + "Oct." : "Окт.", + "Nov." : "Нояб.", + "Dec." : "Дек.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "В составе имени пользователя допускаются следующие символы: «a–z», «A–Z», «0–9» и «_.@-'»", + "A valid username must be provided" : "Укажите допустимое имя пользователя", + "Username contains whitespace at the beginning or at the end" : "Имя пользователя содержит пробел в начале или в конце", + "Username must not consist of dots only" : "Имя пользователя должно состоять не только из точек", + "Username is invalid because files already exist for this user" : "Это имя не может быть использовано, файлы этого пользователя уже существуют", + "A valid password must be provided" : "Укажите допустимый пароль", + "The username is already being used" : "Имя пользователя уже используется", + "Could not create user" : "Не удалось создать пользователя", + "User disabled" : "Пользователь отключен", + "Login canceled by app" : "Вход отменен приложением", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Приложение «%1$s» не может быть установлено, так как не удовлетворены следующие зависимости: %2$s", + "a safe home for all your data" : "надёжный дом для всех ваших данных", + "File is currently busy, please try again later" : "Файл в данный момент используется, повторите попытку позже.", + "Can't read file" : "Не удается прочитать файл", + "Application is not enabled" : "Приложение не разрешено", + "Authentication error" : "Ошибка аутентификации", + "Token expired. Please reload page." : "Токен просрочен. Перезагрузите страницу.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Не установлены драйвера баз данных (sqlite, mysql или postgresql)", + "Cannot write into \"config\" directory" : "Запись в каталог «config» невозможна", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации. Изучите %s.", + "Cannot write into \"apps\" directory" : "Запись в каталог «app» невозможна", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Обычно это можно исправить, предоставив веб-серверу права для записи в каталог приложений или отключив магазин приложений в файле конфигурации.", + "Cannot create \"data\" directory" : "Невозможно создать каталог «data»", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в корневой каталог. Смотрите %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Разрешения обычно можно исправить, предоставив веб-серверу право на запись в корневой каталог. Смотрите %s.", + "Setting locale to %s failed" : "Установка локали %s не удалась", + "Please install one of these locales on your system and restart your webserver." : "Установите один из этих языковых пакетов на вашу систему и перезапустите веб-сервер.", + "PHP module %s not installed." : "Не установлен PHP-модуль %s.", + "Please ask your server administrator to install the module." : "Попросите администратора сервера установить этот модуль.", + "PHP setting \"%s\" is not set to \"%s\"." : "Параметру PHP «%s» не присвоено значение «%s».", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Настройка этого параметра в файле php.ini восстановит работоспособность Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload установлен в «%s», при этом требуется «0»", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Чтобы исправить эту проблему установите параметр mbstring.func_overload в значение 0 в php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Требуется как минимум libxml2 версии 2.7.0. На данный момент установлена %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Для исправления этой ошибки обновите версию libxml2 и перезапустите ваш веб-сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Очевидно, PHP настроен на вычищение блоков встроенной документации. Это сделает несколько центральных приложений недоступными.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Возможно это вызвано кешем/ускорителем вроде Zend OPcache или eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Модули PHP были установлены, но они все еще перечислены как недостающие?", + "Please ask your server administrator to restart the web server." : "Пожалуйста, попросите вашего администратора перезапустить веб-сервер.", + "PostgreSQL >= 9 required" : "Требуется PostgreSQL >= 9", + "Please upgrade your database version" : "Обновите базу данных", + "Your data directory is readable by other users" : "Каталог данных доступен для чтения другим пользователям", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Измените права доступа на 0770, чтобы другие пользователи не могли получить список файлов этого каталога.", + "Your data directory must be an absolute path" : "Каталог данных должен быть указан в виде абсолютного пути", + "Check the value of \"datadirectory\" in your configuration" : "Проверьте в значение параметра «datadirectory» в файле конфигурации.", + "Your data directory is invalid" : "Каталог данных не верен", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Убедитесь, что в корне каталога данных присутствует файл «.ocdata».", + "Action \"%s\" not supported or implemented." : "Действие «%s» не поддерживается или не реализовано.", + "Authentication failed, wrong token or provider ID given" : "Ошибка аутентификации, неверный токен или идентификатор провайдера", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Отсутствуют параметры для завершения запроса. Отсутствующие параметры: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Идентификатор «%1$s» уже поставщиком услуг межсерверного обмена «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Провайдер облачной федерации с идентификатором: \"%s\" не существует.", + "Could not obtain lock type %d on \"%s\"." : "Не удалось получить блокировку типа %d для «%s»", + "Storage unauthorized. %s" : "Хранилище неавторизовано. %s", + "Storage incomplete configuration. %s" : "Неполная конфигурация хранилища. %s", + "Storage connection error. %s" : "Ошибка подключения к хранилищу. %s", + "Storage is temporarily not available" : "Хранилище временно недоступно", + "Storage connection timeout. %s" : "Истекло время ожидания подключения к хранилищу. %s", + "Following databases are supported: %s" : "Поддерживаются следующие СУБД: %s", + "Following platforms are supported: %s" : "Поддерживаются следующие платформы: %s", + "Overview" : "Общие сведения", + "Basic settings" : "Основные параметры", + "Sharing" : "Общий доступ", + "Security" : "Безопасность", + "Groupware" : "ПО совместного использования", + "Personal info" : "Личная информация", + "Mobile & desktop" : "Клиенты для ПК и мобильных устройств", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог приложений или отключив магазин приложений в файле конфигурации. Смотрите %s" +}, +"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ru.json b/docker/overlays/nextcloud/html/lib/l10n/ru.json new file mode 100644 index 0000000..e9449fc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ru.json @@ -0,0 +1,237 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Невозможно выполнить запись в каталог «config».", + "This can usually be fixed by giving the webserver write access to the config directory" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Если для файла «config.php» должен быть установлен режим «только чтение», задайте параметру \"config_is_read_only\" значение \"true\".", + "See %s" : "Обратитесь к %s.", + "This can usually be fixed by giving the webserver write access to the config directory." : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Если для файла «config.php» должен быть установлен режим «только чтение», задайте параметру \"config_is_read_only\" значение \"true\". Изучите %s.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файлы приложения %1$s не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.", + "Sample configuration detected" : "Обнаружена конфигурация из примера.", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Была обнаружена конфигурация из примера. Такая конфигурация не поддерживается и может повредить вашей системе. Прочтите документацию перед внесением изменений в файл config.php", + "Other activities" : "Другие события", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Набор приложений для образовательных учреждений", + "Enterprise bundle" : "Набор приложений для предприятий", + "Groupware bundle" : "Набор приложений для совместной работы", + "Hub bundle" : "Основной набор приложений", + "Social sharing bundle" : "Набор приложений для публикации с использованием соц. сетей", + "PHP %s or higher is required." : "Требуется PHP версии %s или выше.", + "PHP with a version lower than %s is required." : "Требуется PHP версии ниже %s.", + "%sbit or higher PHP required." : "Требуется PHP с разрядностью %s бит или более.", + "The following databases are supported: %s" : "Поддерживаются следующие базы данных: %s", + "The command line tool %s could not be found" : "Утилита командной строки %s не найдена", + "The library %s is not available." : "Библиотека «%s» недоступна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Требуется библиотека «%1$s» версии не меньше %2$s, установлена версия %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Требуется библиотека «%1$s» версии не выше %2$s, установлена версия %3$s.", + "The following platforms are supported: %s" : "Поддерживаются следующие платформы: %s", + "Server version %s or higher is required." : "Требуется сервер версии %s или выше.", + "Server version %s or lower is required." : "Требуется сервер версии %s или ниже.", + "Logged in user must be an admin or sub admin" : "Вошедший в систему пользователь должен обладать правами администратора или суб-администратора", + "Logged in user must be an admin" : "Вошедший в систему пользователь должен обладать правами администратора", + "Wiping of device %s has started" : "Удаление данных с устройства «%s».", + "Wiping of device »%s« has started" : "Удаление данных с устройства «%s».", + "»%s« started remote wipe" : "Начало удаления данных с устройства «%s»", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "На устройстве или в приложении «%s» был начат процесс удаления данных. После его завершения будет отправлено ещё одно электронное письмо.", + "Wiping of device %s has finished" : "Удаление данных с устройства «%s» завершено.", + "Wiping of device »%s« has finished" : "Удаление данных с устройства «%s» завершено.", + "»%s« finished remote wipe" : "Удаление данных с устройства «%s» завершено", + "Device or application »%s« has finished the remote wipe process." : "Процесс удаления данных на устройстве или в приложении «%s» завершён.", + "Remote wipe started" : "Запущен процесс удаления данных с устройства", + "A remote wipe was started on device %s" : "Запущен процесс удаления данных с устройства «%s»", + "Remote wipe finished" : "Процесс удаления данных с устройства завершён", + "The remote wipe on %s has finished" : "Процесс удаления данных с устройства «%s» завершён", + "Authentication" : "Аутентификация", + "Unknown filetype" : "Неизвестный тип файла", + "Invalid image" : "Изображение повреждено", + "Avatar image is not square" : "Изображение аватара не квадратное", + "today" : "сегодня", + "tomorrow" : "завтра", + "yesterday" : "вчера", + "_in %n day_::_in %n days_" : ["через %n день","через %n дня","через %n дней","через %n дня"], + "_%n day ago_::_%n days ago_" : ["%n день назад","%n дня назад","%n дней назад","%n дня назад"], + "next month" : "в следующем месяце", + "last month" : "в прошлом месяце", + "_in %n month_::_in %n months_" : ["через %n месяц","через %n месяца","через %n месяцев","через %n месяца"], + "_%n month ago_::_%n months ago_" : ["%n месяц назад","%n месяца назад","%n месяцев назад","%n месяца назад"], + "next year" : "в следующем году", + "last year" : "в прошлом году", + "_in %n year_::_in %n years_" : ["через %n год","через %n года","через %n лет","через %n года"], + "_%n year ago_::_%n years ago_" : ["%n год назад","%n года назад","%n лет назад","%n года назад"], + "_in %n hour_::_in %n hours_" : ["через %n час","через %n часа","через %n часов","через %n часа"], + "_%n hour ago_::_%n hours ago_" : ["%n час назад","%n часа назад","%n часов назад","%n часа назад"], + "_in %n minute_::_in %n minutes_" : ["через %n минуту","через %n минуты","через %n минут","через %n минуты"], + "_%n minute ago_::_%n minutes ago_" : ["%n минуту назад","%n минуты назад","%n минут назад","%n минуты назад"], + "in a few seconds" : "через несколько секунд", + "seconds ago" : "несколько секунд назад", + "Empty file" : "Пустой файл", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль с ID «%s» не существует. Включите его в настройках приложений или обратитесь к администратору.", + "File name is a reserved word" : "Имя файла является зарезервированным словом", + "File name contains at least one invalid character" : "Имя файла содержит по крайней мере один некорректный символ", + "File name is too long" : "Имя файла слишком длинное.", + "Dot files are not allowed" : "Файлы начинающиеся с точки не допускаются", + "Empty filename is not allowed" : "Пустое имя файла не допускается", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Приложение «%s» не может быть установлено, так как файл с информацией о приложении не может быть прочтен.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Приложение «%s» не может быть установлено, потому что оно несовместимо с этой версией сервера", + "__language_name__" : "Русский", + "This is an automatically sent email, please do not reply." : "Это сообщение отправлено автоматически и не требует ответа.", + "Help" : "Помощь", + "Apps" : "Приложения", + "Settings" : "Настройки", + "Log out" : "Выйти", + "Users" : "Пользователи", + "Unknown user" : "Неизвестный пользователь", + "Additional settings" : "Дополнительные настройки", + "%s enter the database username and name." : "%s укажите имя пользователя и название для базы данных.", + "%s enter the database username." : "%s введите имя пользователя базы данных.", + "%s enter the database name." : "%s введите имя базы данных.", + "%s you may not use dots in the database name" : "%s Вы не можете использовать точки в имени базы данных", + "MySQL username and/or password not valid" : "Неверное имя пользователя и/или пароль для подключения к MySQL", + "You need to enter details of an existing account." : "Необходимо уточнить данные существующего акаунта.", + "Oracle connection could not be established" : "Соединение с Oracle не может быть установлено", + "Oracle username and/or password not valid" : "Неверное имя пользователя и/или пароль Oracle", + "PostgreSQL username and/or password not valid" : "Неверное имя пользователя и/или пароль PostgreSQL", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не поддерживается и %s может работать некорректно на данной платформе. Используйте на свой страх и риск!", + "For the best results, please consider using a GNU/Linux server instead." : "Для достижения наилучших результатов, рассмотрите вариант использования сервера на GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Кажется что экземпляр этого %s работает в 32-битной среде PHP и в php.ini был настроен open_basedir. Это приведёт к проблемам с файлами более 4 ГБ и настоятельно не рекомендуется.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Пожалуйста, удалите директиву open_basedir из файла php.ini или смените PHP на 64-разрядную сборку.", + "Set an admin username." : "Задать имя пользователя для администратора.", + "Set an admin password." : "Задать пароль для admin.", + "Can't create or write into the data directory %s" : "Невозможно создать или записать в каталог данных %s", + "Invalid Federated Cloud ID" : "Неверный ID в объединении облачных хранилищ.", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Бэкенд общего доступа %s должен реализовывать интерфейс OCP\\Share_Backend", + "Sharing backend %s not found" : "Механизм предоставления общего доступа %s не найден", + "Sharing backend for %s not found" : "Не найден механизм предоставления общего доступа для %s ", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить", + "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ", + "Open »%s«" : "Открыть «%s»", + "%1$s via %2$s" : "%1$s через %2$s", + "You are not allowed to share %s" : "Вам не разрешено делиться %s", + "Can’t increase permissions of %s" : "Невозможно увеличить права доступа для %s", + "Files can’t be shared with delete permissions" : "Файлы не могут иметь общий доступ с правами на удаление", + "Files can’t be shared with create permissions" : "Файлы не могут иметь общий доступ с правами на создание", + "Expiration date is in the past" : "Дата окончания срока действия уже прошла", + "Can’t set expiration date more than %s days in the future" : "Невозможно установить дату окончания срока действия более %s дней", + "%1$s shared »%2$s« with you" : "%1$s предоставил(а) вам доступ к «%2$s»", + "%1$s shared »%2$s« with you." : "%1$s предоставил(а) вам доступ к «%2$s».", + "Click the button below to open it." : "Нажмите расположенную ниже кнопку для перехода к полученному общему ресурсу.", + "The requested share does not exist anymore" : "Запрошенный общий ресурс более не существует.", + "Could not find category \"%s\"" : "Категория «%s» не найдена", + "Sunday" : "Воскресенье", + "Monday" : "Понедельник", + "Tuesday" : "Вторник", + "Wednesday" : "Среда", + "Thursday" : "Четверг", + "Friday" : "Пятница", + "Saturday" : "Суббота", + "Sun." : "Вс.", + "Mon." : "Пн.", + "Tue." : "Вт.", + "Wed." : "Ср.", + "Thu." : "Чт.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "Su" : "Вс", + "Mo" : "Пн", + "Tu" : "Вт", + "We" : "Ср", + "Th" : "Чт", + "Fr" : "Пт", + "Sa" : "Сб", + "January" : "Январь", + "February" : "Февраль", + "March" : "Март", + "April" : "Апрель", + "May" : "Май", + "June" : "Июнь", + "July" : "Июль", + "August" : "Август", + "September" : "Сентябрь", + "October" : "Октябрь", + "November" : "Ноябрь", + "December" : "Декабрь", + "Jan." : "Янв.", + "Feb." : "Фев.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Май", + "Jun." : "Июн.", + "Jul." : "Июл.", + "Aug." : "Авг.", + "Sep." : "Сен.", + "Oct." : "Окт.", + "Nov." : "Нояб.", + "Dec." : "Дек.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "В составе имени пользователя допускаются следующие символы: «a–z», «A–Z», «0–9» и «_.@-'»", + "A valid username must be provided" : "Укажите допустимое имя пользователя", + "Username contains whitespace at the beginning or at the end" : "Имя пользователя содержит пробел в начале или в конце", + "Username must not consist of dots only" : "Имя пользователя должно состоять не только из точек", + "Username is invalid because files already exist for this user" : "Это имя не может быть использовано, файлы этого пользователя уже существуют", + "A valid password must be provided" : "Укажите допустимый пароль", + "The username is already being used" : "Имя пользователя уже используется", + "Could not create user" : "Не удалось создать пользователя", + "User disabled" : "Пользователь отключен", + "Login canceled by app" : "Вход отменен приложением", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Приложение «%1$s» не может быть установлено, так как не удовлетворены следующие зависимости: %2$s", + "a safe home for all your data" : "надёжный дом для всех ваших данных", + "File is currently busy, please try again later" : "Файл в данный момент используется, повторите попытку позже.", + "Can't read file" : "Не удается прочитать файл", + "Application is not enabled" : "Приложение не разрешено", + "Authentication error" : "Ошибка аутентификации", + "Token expired. Please reload page." : "Токен просрочен. Перезагрузите страницу.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Не установлены драйвера баз данных (sqlite, mysql или postgresql)", + "Cannot write into \"config\" directory" : "Запись в каталог «config» невозможна", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог конфигурации. Изучите %s.", + "Cannot write into \"apps\" directory" : "Запись в каталог «app» невозможна", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Обычно это можно исправить, предоставив веб-серверу права для записи в каталог приложений или отключив магазин приложений в файле конфигурации.", + "Cannot create \"data\" directory" : "Невозможно создать каталог «data»", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в корневой каталог. Смотрите %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Разрешения обычно можно исправить, предоставив веб-серверу право на запись в корневой каталог. Смотрите %s.", + "Setting locale to %s failed" : "Установка локали %s не удалась", + "Please install one of these locales on your system and restart your webserver." : "Установите один из этих языковых пакетов на вашу систему и перезапустите веб-сервер.", + "PHP module %s not installed." : "Не установлен PHP-модуль %s.", + "Please ask your server administrator to install the module." : "Попросите администратора сервера установить этот модуль.", + "PHP setting \"%s\" is not set to \"%s\"." : "Параметру PHP «%s» не присвоено значение «%s».", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Настройка этого параметра в файле php.ini восстановит работоспособность Nextcloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload установлен в «%s», при этом требуется «0»", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Чтобы исправить эту проблему установите параметр mbstring.func_overload в значение 0 в php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Требуется как минимум libxml2 версии 2.7.0. На данный момент установлена %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Для исправления этой ошибки обновите версию libxml2 и перезапустите ваш веб-сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Очевидно, PHP настроен на вычищение блоков встроенной документации. Это сделает несколько центральных приложений недоступными.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Возможно это вызвано кешем/ускорителем вроде Zend OPcache или eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Модули PHP были установлены, но они все еще перечислены как недостающие?", + "Please ask your server administrator to restart the web server." : "Пожалуйста, попросите вашего администратора перезапустить веб-сервер.", + "PostgreSQL >= 9 required" : "Требуется PostgreSQL >= 9", + "Please upgrade your database version" : "Обновите базу данных", + "Your data directory is readable by other users" : "Каталог данных доступен для чтения другим пользователям", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Измените права доступа на 0770, чтобы другие пользователи не могли получить список файлов этого каталога.", + "Your data directory must be an absolute path" : "Каталог данных должен быть указан в виде абсолютного пути", + "Check the value of \"datadirectory\" in your configuration" : "Проверьте в значение параметра «datadirectory» в файле конфигурации.", + "Your data directory is invalid" : "Каталог данных не верен", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Убедитесь, что в корне каталога данных присутствует файл «.ocdata».", + "Action \"%s\" not supported or implemented." : "Действие «%s» не поддерживается или не реализовано.", + "Authentication failed, wrong token or provider ID given" : "Ошибка аутентификации, неверный токен или идентификатор провайдера", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Отсутствуют параметры для завершения запроса. Отсутствующие параметры: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Идентификатор «%1$s» уже поставщиком услуг межсерверного обмена «%2$s»", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Провайдер облачной федерации с идентификатором: \"%s\" не существует.", + "Could not obtain lock type %d on \"%s\"." : "Не удалось получить блокировку типа %d для «%s»", + "Storage unauthorized. %s" : "Хранилище неавторизовано. %s", + "Storage incomplete configuration. %s" : "Неполная конфигурация хранилища. %s", + "Storage connection error. %s" : "Ошибка подключения к хранилищу. %s", + "Storage is temporarily not available" : "Хранилище временно недоступно", + "Storage connection timeout. %s" : "Истекло время ожидания подключения к хранилищу. %s", + "Following databases are supported: %s" : "Поддерживаются следующие СУБД: %s", + "Following platforms are supported: %s" : "Поддерживаются следующие платформы: %s", + "Overview" : "Общие сведения", + "Basic settings" : "Основные параметры", + "Sharing" : "Общий доступ", + "Security" : "Безопасность", + "Groupware" : "ПО совместного использования", + "Personal info" : "Личная информация", + "Mobile & desktop" : "Клиенты для ПК и мобильных устройств", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Обычно это можно исправить, предоставив веб-серверу права на запись в каталог приложений или отключив магазин приложений в файле конфигурации. Смотрите %s" +},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/si_LK.js b/docker/overlays/nextcloud/html/lib/l10n/si_LK.js new file mode 100644 index 0000000..968604f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/si_LK.js @@ -0,0 +1,61 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "ගොනු මාදිලිය දෝෂ සහිතයි", + "Invalid image" : "පින්තුරය දෝෂ සහිතයි", + "today" : "අද", + "yesterday" : "ඊයේ", + "last month" : "පෙර මාසයේ", + "last year" : "පෙර අවුරුද්දේ", + "seconds ago" : "තත්පරයන්ට පෙර", + "__language_name__" : "සිංහල", + "Help" : "උදව්", + "Apps" : "යෙදුම්", + "Settings" : "සැකසුම්", + "Log out" : "නික්මීම", + "Users" : "පරිශීලකයන්", + "Unknown user" : "හදුනානොගත් සේවාදායකයා", + "Sunday" : "ඉරිදා", + "Monday" : "සඳුදා", + "Tuesday" : "අඟහරුවාදා", + "Wednesday" : "බදාදා", + "Thursday" : "බ්‍රහස්පතින්දා", + "Friday" : "සිකුරාදා", + "Saturday" : "සෙනසුරාදා", + "Sun." : "ඉරිදා", + "Mon." : "සඳුදා", + "Tue." : "අඟ.", + "Wed." : "බදාදා", + "Thu." : "බ්‍රහස්.", + "Fri." : "සිකු.", + "Sat." : "සෙන.", + "January" : "ජනවාරි", + "February" : "පෙබරවාරි", + "March" : "මාර්තු", + "April" : "අප්‍රේල්", + "May" : "මැයි", + "June" : "ජූනි", + "July" : "ජූලි", + "August" : "අගෝස්තු", + "September" : "සැප්තැම්බර්", + "October" : "ඔක්තෝබර", + "November" : "නොවැම්බර්", + "December" : "දෙසැම්බර්", + "Jan." : "ජන.", + "Feb." : "පෙබ.", + "Mar." : "මාර්තු", + "Apr." : "අප්‍රේල්", + "May." : "මැයි", + "Jun." : "ජුනි", + "Jul." : "ජුලි", + "Aug." : "අගෝ.", + "Sep." : "සැප්.", + "Oct." : "ඔක්.", + "Nov." : "නොවැ.", + "Dec." : "දෙසැ.", + "Application is not enabled" : "යෙදුම සක්‍රිය කර නොමැත", + "Authentication error" : "සත්‍යාපන දෝෂයක්", + "Token expired. Please reload page." : "ටෝකනය කල් ඉකුත් වී ඇත. පිටුව නැවුම් කරන්න", + "Sharing" : "හුවමාරු කිරීම" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/si_LK.json b/docker/overlays/nextcloud/html/lib/l10n/si_LK.json new file mode 100644 index 0000000..c063d6e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/si_LK.json @@ -0,0 +1,59 @@ +{ "translations": { + "Unknown filetype" : "ගොනු මාදිලිය දෝෂ සහිතයි", + "Invalid image" : "පින්තුරය දෝෂ සහිතයි", + "today" : "අද", + "yesterday" : "ඊයේ", + "last month" : "පෙර මාසයේ", + "last year" : "පෙර අවුරුද්දේ", + "seconds ago" : "තත්පරයන්ට පෙර", + "__language_name__" : "සිංහල", + "Help" : "උදව්", + "Apps" : "යෙදුම්", + "Settings" : "සැකසුම්", + "Log out" : "නික්මීම", + "Users" : "පරිශීලකයන්", + "Unknown user" : "හදුනානොගත් සේවාදායකයා", + "Sunday" : "ඉරිදා", + "Monday" : "සඳුදා", + "Tuesday" : "අඟහරුවාදා", + "Wednesday" : "බදාදා", + "Thursday" : "බ්‍රහස්පතින්දා", + "Friday" : "සිකුරාදා", + "Saturday" : "සෙනසුරාදා", + "Sun." : "ඉරිදා", + "Mon." : "සඳුදා", + "Tue." : "අඟ.", + "Wed." : "බදාදා", + "Thu." : "බ්‍රහස්.", + "Fri." : "සිකු.", + "Sat." : "සෙන.", + "January" : "ජනවාරි", + "February" : "පෙබරවාරි", + "March" : "මාර්තු", + "April" : "අප්‍රේල්", + "May" : "මැයි", + "June" : "ජූනි", + "July" : "ජූලි", + "August" : "අගෝස්තු", + "September" : "සැප්තැම්බර්", + "October" : "ඔක්තෝබර", + "November" : "නොවැම්බර්", + "December" : "දෙසැම්බර්", + "Jan." : "ජන.", + "Feb." : "පෙබ.", + "Mar." : "මාර්තු", + "Apr." : "අප්‍රේල්", + "May." : "මැයි", + "Jun." : "ජුනි", + "Jul." : "ජුලි", + "Aug." : "අගෝ.", + "Sep." : "සැප්.", + "Oct." : "ඔක්.", + "Nov." : "නොවැ.", + "Dec." : "දෙසැ.", + "Application is not enabled" : "යෙදුම සක්‍රිය කර නොමැත", + "Authentication error" : "සත්‍යාපන දෝෂයක්", + "Token expired. Please reload page." : "ටෝකනය කල් ඉකුත් වී ඇත. පිටුව නැවුම් කරන්න", + "Sharing" : "හුවමාරු කිරීම" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sk.js b/docker/overlays/nextcloud/html/lib/l10n/sk.js new file mode 100644 index 0000000..9e5c94b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sk.js @@ -0,0 +1,239 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nie je možné zapisovat do priečinka \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do priečinka s konfiguráciou.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Alebo, ak preferujete ponechať súbor config.php iba na čítanie, nastavte v ňom parameter \"config_is_read_only\" na true.", + "See %s" : "Pozri %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Toto sa zvyčajne dá opraviť poskytnutím práva webového servera na zápis do priečinka s nastaveniami.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Alebo, ak preferujete ponechať súbor config.php iba na čítanie, nastavte v ňom parameter \"config_is_read_only\" na true. Viď %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Súbory aplikácie %1$s neboli správne nahradené. Uistite sa, že to je verzia kompatibilná so serverom.", + "Sample configuration detected" : "Detekovaná bola vzorová konfigurácia", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Zistilo sa, že konfigurácia bola skopírovaná zo vzorových súborov. Takáto konfigurácia nie je podporovaná a môže poškodiť vašu inštaláciu. Prečítajte si dokumentáciu pred vykonaním zmien v config.php", + "Other activities" : "Iné aktivity", + "%1$s and %2$s" : "%1$s a %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s a %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s a %5$s", + "Education Edition" : "Edícia pre výuku", + "Enterprise bundle" : "Enterprise balíček", + "Groupware bundle" : "Balíček groupware", + "Hub bundle" : "Sada pre centrum aktivity (hub)", + "Social sharing bundle" : "Balíček sociálneho zdieľania", + "PHP %s or higher is required." : "Požadovaná verzia PHP %s alebo vyššia.", + "PHP with a version lower than %s is required." : "PHP je vyžadované vo vyššej verzii ako %s.", + "%sbit or higher PHP required." : "%sbit alebo vyššie PHP je vyžadované.", + "The following databases are supported: %s" : "Podporované sú nasledujúce databázy: %s", + "The command line tool %s could not be found" : "Nástroj príkazového riadka %s nebol nájdený", + "The library %s is not available." : "Knižnica %s je nedostupná.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Požadovaná je knižnica %1$s vo vyššej verzii ako %2$s - dostupná verzia %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Požadovaná je knižnica %1$s v nižšej verzii ako %2$s - dostupná verzia %3$s.", + "The following platforms are supported: %s" : "Podporované sú nasledujúce platformy: %s", + "Server version %s or higher is required." : "Je vyžadovaná verzia servera %s alebo vyššia.", + "Server version %s or lower is required." : "Je vyžadovaná verzia servera %s alebo nižšia.", + "Logged in user must be an admin or sub admin" : "Prihlásený používateľ musí byť správcom alebo správcom pre čiastkovú oblasť.", + "Logged in user must be an admin" : "Prihlásený používateľ musí byť správca", + "Wiping of device %s has started" : "Začalo sa mazanie zariadenia %s", + "Wiping of device »%s« has started" : "Začalo sa mazanie zariadenia „%s“", + "»%s« started remote wipe" : "„%s“ začalo mazanie na diaľku", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Zariadenie alebo aplikácia „%s“ začal proces mazania na diaľku. Po dokončení procesu dostanete ďalší e-mail", + "Wiping of device %s has finished" : "Mazanie zariadenia %s bolo dokončené.", + "Wiping of device »%s« has finished" : "Mazanie zariadenia „%s“ bolo dokončené.", + "»%s« finished remote wipe" : "„%s“ dokončilo mazanie na diaľku ", + "Device or application »%s« has finished the remote wipe process." : "Zariadenie alebo aplikácia „%s“ dokončilo(a) proces mazania na diaľku.", + "Remote wipe started" : "Začalo mazanie na diaľku", + "A remote wipe was started on device %s" : "Na zariadeni %s začalo mazanie na diaľku", + "Remote wipe finished" : "Mazanie na diaľku bolo dokončené", + "The remote wipe on %s has finished" : "Mazanie na diaľku na %s bolo dokončené", + "Authentication" : "Autentifikácia", + "Unknown filetype" : "Neznámy typ súboru", + "Invalid image" : "Chybný obrázok", + "Avatar image is not square" : "Obrázok avatara nie je štvorcový", + "today" : "dnes", + "tomorrow" : "zajtra", + "yesterday" : "včera", + "_in %n day_::_in %n days_" : ["o %n deň","o %n dni","o %n dní","o %n dní"], + "_%n day ago_::_%n days ago_" : ["včera","pred %n dňami","pred %n dňami","pred %n dňami"], + "next month" : "budúci mesiac", + "last month" : "minulý mesiac", + "_in %n month_::_in %n months_" : ["o %n mesiac ","o %n mesiace","o %n mesiacov","o %n mesiacov"], + "_%n month ago_::_%n months ago_" : ["pred %n mesiacom","pred %n mesiacmi","pred %n mesiacmi","pred %n mesiacmi"], + "next year" : "budúci rok", + "last year" : "minulý rok", + "_in %n year_::_in %n years_" : ["o %n rok","o %n roky","o %n rokov","o %n rokov"], + "_%n year ago_::_%n years ago_" : ["vlani","pred %n rokmi","pred %n rokmi","pred %n rokmi"], + "_in %n hour_::_in %n hours_" : ["o %n hodinu","o %n hodiny","o %n hodín","o %n hodín"], + "_%n hour ago_::_%n hours ago_" : ["pred %n hodinou","pred %n hodinami","pred %n hodinami","pred %n hodinami"], + "_in %n minute_::_in %n minutes_" : ["o %n minútu","o %n minúty","o %n minút","o %n minút"], + "_%n minute ago_::_%n minutes ago_" : ["pred %n minútou","pred %n minútami","pred %n minútami","pred %n minútami"], + "in a few seconds" : "o pár sekúnd", + "seconds ago" : "pred sekundami", + "Empty file" : "Prázdny súbor", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s ID: %s neexistuje. Povoľte ho prosím vo vašom nastavení aplikácií alebo kontaktujte správcu.", + "File name is a reserved word" : "Názov súboru je rezervované slovo.", + "File name contains at least one invalid character" : "Názov súboru obsahuje nepovolené znaky.", + "File name is too long" : "Meno súboru je veľmi dlhé.", + "Dot files are not allowed" : "Názov súboru začínajúci bodkou nie je povolený.", + "Empty filename is not allowed" : "Prázdny názov súboru nie je povolený", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikáciu \"%s\" nie je možné nainštalovať, lebo nebolo možné načítať súbor s informáciami o aplikácií.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikácia \"%s\" nie je kompatibilná s verziou servera, preto nemôže byť nainštalovaná.", + "__language_name__" : "Slovenčina", + "This is an automatically sent email, please do not reply." : "Toto je automaticky odosielaný e-mail, prosím, neodpovedajte.", + "Help" : "Pomoc", + "Apps" : "Aplikácie", + "Settings" : "Nastavenia", + "Log out" : "Odhlásiť sa", + "Users" : "Používatelia", + "Unknown user" : "Neznámy používateľ", + "Additional settings" : "Ďalšie nastavenia", + "%s enter the database username and name." : "%s zadajte používateľské meno a meno databázy", + "%s enter the database username." : "Zadajte používateľské meno %s databázy.", + "%s enter the database name." : "Zadajte názov databázy pre %s databázy.", + "%s you may not use dots in the database name" : "V názve databázy %s nemôžete používať bodky", + "MySQL username and/or password not valid" : "Neplatné užívateľské meno a/alebo heslo do MySQL", + "You need to enter details of an existing account." : "Musíte zadať údaje existujúceho účtu.", + "Oracle connection could not be established" : "Nie je možné pripojiť sa k Oracle", + "Oracle username and/or password not valid" : "Používateľské meno a/alebo heslo pre Oracle databázu je neplatné", + "PostgreSQL username and/or password not valid" : "Používateľské meno a/alebo heslo pre PostgreSQL databázu je neplatné", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nie je podporovaný a %s nebude správne fungovať na tejto platforme. Použite ho na vlastné riziko!", + "For the best results, please consider using a GNU/Linux server instead." : "Pre dosiahnutie najlepších výsledkov, prosím zvážte použitie GNU/Linux servera.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Zdá sa, že táto inštancia %s beží v 32-bitovom prostredí PHP a v php.ini bola nastavená voľba open_basedir. To bude zdrojom problémov so súbormi väčšími ako 4GB a dôrazne sa neodporúča.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Prosím, odstráňte nastavenie open_basedir vo vašom php.ini alebo prejdite na 64-bit PHP.", + "Set an admin username." : "Zadajte používateľské meno administrátora.", + "Set an admin password." : "Zadajte heslo administrátora.", + "Can't create or write into the data directory %s" : "Nemožno vytvoriť alebo zapisovať do priečinka dát %s", + "Invalid Federated Cloud ID" : "Neplatné združené Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend pre sprístupnenie %s musí implementovať rozhranie OCP\\Share_Backend", + "Sharing backend %s not found" : "Backend sprístupnenia %s nebol nájdený", + "Sharing backend for %s not found" : "Backend sprístupnenia pre %s nebol nájdený", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s vám sprístupnil »%2$s« s poznámkou:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s vám sprístupnil »%2$s« s poznámkou", + "»%s« added a note to a file shared with you" : "»%s« pridal poznámku k súboru ktorý s Vami zdieľa", + "Open »%s«" : "Otvoriť »%s«", + "%1$s via %2$s" : "%1$s cez %2$s", + "You are not allowed to share %s" : "Nemôžete sprístupniť %s", + "Can’t increase permissions of %s" : "Nie je možné navýšiť oprávnenia pre %s", + "Files can’t be shared with delete permissions" : "Súbory nie je možné sprístupňovať s oprávneniami na odstránenie", + "Files can’t be shared with create permissions" : "Súbory nie je možné sprístupňovať s oprávneniami na vytváranie", + "Expiration date is in the past" : "Dátum konca platnosti je v minulosti", + "Can’t set expiration date more than %s days in the future" : "Nie je možné nastaviť dátum konca platnosti viac ako %s dní v budúcnosti", + "%1$s shared »%2$s« with you" : "%1$s vám sprístupnil »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s vám sprístupnil »%2$s«.", + "Click the button below to open it." : "Pre otvorenie klienta kliknite na tlačítko nižšie.", + "The requested share does not exist anymore" : "Požadované sprístupnenie už neexistuje", + "Could not find category \"%s\"" : "Nemožno nájsť danú kategóriu \"%s\"", + "Sunday" : "Nedeľa", + "Monday" : "Pondelok", + "Tuesday" : "Utorok", + "Wednesday" : "Streda", + "Thursday" : "Štvrtok", + "Friday" : "Piatok", + "Saturday" : "Sobota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Uto.", + "Wed." : "Str.", + "Thu." : "Štv.", + "Fri." : "Pia.", + "Sat." : "Sob.", + "Su" : "Ne", + "Mo" : "Po", + "Tu" : "Ut", + "We" : "St", + "Th" : "Št", + "Fr" : "Pi", + "Sa" : "So", + "January" : "Január", + "February" : "Február", + "March" : "Marec", + "April" : "Apríl", + "May" : "Máj", + "June" : "Jún", + "July" : "Júl", + "August" : "August", + "September" : "September", + "October" : "Október", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Máj.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V mene používateľa je možné použiť iba nasledovné znaky: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Musíte zadať platné používateľské meno", + "Username contains whitespace at the beginning or at the end" : "Meno používateľa obsahuje na začiatku, alebo na konci medzeru", + "Username must not consist of dots only" : "Používateľské meno sa nesmie skladať len z bodiek", + "Username is invalid because files already exist for this user" : "Používateľské meno je neplatné, pretože pre tohto používateľa už existujú súbory", + "A valid password must be provided" : "Musíte zadať platné heslo", + "The username is already being used" : "Meno používateľa je už použité", + "Could not create user" : "Nepodarilo sa vytvoriť používateľa", + "User disabled" : "Používateľ zakázaný", + "Login canceled by app" : "Prihlásenie bolo zrušené aplikáciou", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikáciu \"%1$s\" nie je možné inštalovať, pretože nie sú splnené nasledovné závislosti: %2$s", + "a safe home for all your data" : "bezpečný domov pre všetky vaše dáta", + "File is currently busy, please try again later" : "Súbor sa práve používa, skúste prosím neskôr", + "Can't read file" : "Nemožno čítať súbor.", + "Application is not enabled" : "Aplikácia nie je zapnutá", + "Authentication error" : "Chyba autentifikácie", + "Token expired. Please reload page." : "Token vypršal. Obnovte, prosím, stránku.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ovládače databázy (sqlite, mysql, alebo postgresql) nie sú nainštalované.", + "Cannot write into \"config\" directory" : "Nie je možné zapisovať do priečinka \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do adresára s konfiguráciou. Viď %s", + "Cannot write into \"apps\" directory" : "Nie je možné zapisovať do priečinka \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Toto sa zvyčajne dá opraviť poskytnutím práva webového servera na zápis do priečinka s aplikáciami alebo vypnutím katalógu s aplikáciami v súbore s nastaveniami.", + "Cannot create \"data\" directory" : "Nie je možné vytvoriť priečinok \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do koreňového adresára. Viď %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Oprávnenia je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do koreňového priečinka. Viď %s.", + "Setting locale to %s failed" : "Nastavenie locale na %s zlyhalo", + "Please install one of these locales on your system and restart your webserver." : "Prosím, nainštalujte si aspoň jeden z týchto jazykov so svojho systému a reštartujte webserver.", + "PHP module %s not installed." : "PHP modul %s nie je nainštalovaný.", + "Please ask your server administrator to install the module." : "Prosím, požiadajte administrátora vášho servera o inštaláciu modulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Voľba PHP „%s“ nie je nastavená na „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Použitím týchto nastavení v php.ini dovolí Nextcloudu sa znova spustiť", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je nastavený na \"%s\", namiesto predpokladanej hodnoty \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Oprava problému spočíva v nastavení mbstring.func_overload na 0 vo vašom php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Vyžadovaná verzia libxml2 je 2.7.0 a vyššia. Momentálne je nainštalovaná verzia %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pre vyriešenie tohto problému aktualizujte prosím verziu libxml2 a reštartujte webový server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je zjavne nastavené, aby odstraňovalo bloky vloženej dokumentácie. To zneprístupní niekoľko základných aplikácií.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "To je pravdepodobne spôsobené cache/akcelerátorom ako napr. Zend OPcache alebo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly boli nainštalované, ale stále sa tvária, že chýbajú?", + "Please ask your server administrator to restart the web server." : "Prosím, požiadajte administrátora vášho servera o reštartovanie webového servera.", + "PostgreSQL >= 9 required" : "Vyžadované PostgreSQL >= 9", + "Please upgrade your database version" : "Prosím, aktualizujte verziu svojej databázy", + "Your data directory is readable by other users" : "Váš priečinok s dátami je prístupný na čítanie ostatným používateľom", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Prosím, zmeňte oprávnenia na 0770, aby tento priečinok nemohli ostatní používatelia otvoriť.", + "Your data directory must be an absolute path" : "Priečinok s dátami musí byť zadaný ako absolútna cesta", + "Check the value of \"datadirectory\" in your configuration" : "Skontrolujte hodnotu \"datadirectory\" vo vašej konfigurácii", + "Your data directory is invalid" : "Priečinok s dátami je neplatný", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Uistite sa, že v koreňovom adresári sa nachádza súbor s názvom \".ocdata\".", + "Action \"%s\" not supported or implemented." : "Akcia \"%s\" nie je podporovaná alebo implementovaná.", + "Authentication failed, wrong token or provider ID given" : "Autentifikácia zlyhala, bol zadaný neplatný token alebo ID poskytovateľa", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Pre dokončenie požiadavky chýbajú parametre. Chýbajúce parametre: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" je už použité v združenom cloude \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Poskytovateľ združeného cloudu s ID: \"%s\" neexistuje.", + "Could not obtain lock type %d on \"%s\"." : "Nepodarilo sa získať zámok typu %d na „%s“.", + "Storage unauthorized. %s" : "Úložisko neoverené. %s", + "Storage incomplete configuration. %s" : "Neplatná konfigurácia úložiska. %s", + "Storage connection error. %s" : "Chyba pripojenia k úložisku. %s", + "Storage is temporarily not available" : "Úložisko je dočasne nedostupné", + "Storage connection timeout. %s" : "Vypršanie pripojenia k úložisku. %s", + "Following databases are supported: %s" : "Podporované sú tieto databázy: %s", + "Following platforms are supported: %s" : "Podporované sú nasledovné systémy: %s", + "Overview" : "Prehľad", + "Basic settings" : "Základné nastavenia", + "Sharing" : "Sprístupnenie", + "Security" : "Zabezpečenie", + "Groupware" : "Groupware", + "Personal info" : "Osobné informácie", + "Mobile & desktop" : "Mobil a počítač", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Toto je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do priečinka aplikácií alebo vypnete obchod s aplikáciami v konfiguračnom súbore. Viď %s" +}, +"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sk.json b/docker/overlays/nextcloud/html/lib/l10n/sk.json new file mode 100644 index 0000000..cf5f277 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sk.json @@ -0,0 +1,237 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nie je možné zapisovat do priečinka \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do priečinka s konfiguráciou.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Alebo, ak preferujete ponechať súbor config.php iba na čítanie, nastavte v ňom parameter \"config_is_read_only\" na true.", + "See %s" : "Pozri %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Toto sa zvyčajne dá opraviť poskytnutím práva webového servera na zápis do priečinka s nastaveniami.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Alebo, ak preferujete ponechať súbor config.php iba na čítanie, nastavte v ňom parameter \"config_is_read_only\" na true. Viď %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Súbory aplikácie %1$s neboli správne nahradené. Uistite sa, že to je verzia kompatibilná so serverom.", + "Sample configuration detected" : "Detekovaná bola vzorová konfigurácia", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Zistilo sa, že konfigurácia bola skopírovaná zo vzorových súborov. Takáto konfigurácia nie je podporovaná a môže poškodiť vašu inštaláciu. Prečítajte si dokumentáciu pred vykonaním zmien v config.php", + "Other activities" : "Iné aktivity", + "%1$s and %2$s" : "%1$s a %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s a %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s a %5$s", + "Education Edition" : "Edícia pre výuku", + "Enterprise bundle" : "Enterprise balíček", + "Groupware bundle" : "Balíček groupware", + "Hub bundle" : "Sada pre centrum aktivity (hub)", + "Social sharing bundle" : "Balíček sociálneho zdieľania", + "PHP %s or higher is required." : "Požadovaná verzia PHP %s alebo vyššia.", + "PHP with a version lower than %s is required." : "PHP je vyžadované vo vyššej verzii ako %s.", + "%sbit or higher PHP required." : "%sbit alebo vyššie PHP je vyžadované.", + "The following databases are supported: %s" : "Podporované sú nasledujúce databázy: %s", + "The command line tool %s could not be found" : "Nástroj príkazového riadka %s nebol nájdený", + "The library %s is not available." : "Knižnica %s je nedostupná.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Požadovaná je knižnica %1$s vo vyššej verzii ako %2$s - dostupná verzia %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Požadovaná je knižnica %1$s v nižšej verzii ako %2$s - dostupná verzia %3$s.", + "The following platforms are supported: %s" : "Podporované sú nasledujúce platformy: %s", + "Server version %s or higher is required." : "Je vyžadovaná verzia servera %s alebo vyššia.", + "Server version %s or lower is required." : "Je vyžadovaná verzia servera %s alebo nižšia.", + "Logged in user must be an admin or sub admin" : "Prihlásený používateľ musí byť správcom alebo správcom pre čiastkovú oblasť.", + "Logged in user must be an admin" : "Prihlásený používateľ musí byť správca", + "Wiping of device %s has started" : "Začalo sa mazanie zariadenia %s", + "Wiping of device »%s« has started" : "Začalo sa mazanie zariadenia „%s“", + "»%s« started remote wipe" : "„%s“ začalo mazanie na diaľku", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Zariadenie alebo aplikácia „%s“ začal proces mazania na diaľku. Po dokončení procesu dostanete ďalší e-mail", + "Wiping of device %s has finished" : "Mazanie zariadenia %s bolo dokončené.", + "Wiping of device »%s« has finished" : "Mazanie zariadenia „%s“ bolo dokončené.", + "»%s« finished remote wipe" : "„%s“ dokončilo mazanie na diaľku ", + "Device or application »%s« has finished the remote wipe process." : "Zariadenie alebo aplikácia „%s“ dokončilo(a) proces mazania na diaľku.", + "Remote wipe started" : "Začalo mazanie na diaľku", + "A remote wipe was started on device %s" : "Na zariadeni %s začalo mazanie na diaľku", + "Remote wipe finished" : "Mazanie na diaľku bolo dokončené", + "The remote wipe on %s has finished" : "Mazanie na diaľku na %s bolo dokončené", + "Authentication" : "Autentifikácia", + "Unknown filetype" : "Neznámy typ súboru", + "Invalid image" : "Chybný obrázok", + "Avatar image is not square" : "Obrázok avatara nie je štvorcový", + "today" : "dnes", + "tomorrow" : "zajtra", + "yesterday" : "včera", + "_in %n day_::_in %n days_" : ["o %n deň","o %n dni","o %n dní","o %n dní"], + "_%n day ago_::_%n days ago_" : ["včera","pred %n dňami","pred %n dňami","pred %n dňami"], + "next month" : "budúci mesiac", + "last month" : "minulý mesiac", + "_in %n month_::_in %n months_" : ["o %n mesiac ","o %n mesiace","o %n mesiacov","o %n mesiacov"], + "_%n month ago_::_%n months ago_" : ["pred %n mesiacom","pred %n mesiacmi","pred %n mesiacmi","pred %n mesiacmi"], + "next year" : "budúci rok", + "last year" : "minulý rok", + "_in %n year_::_in %n years_" : ["o %n rok","o %n roky","o %n rokov","o %n rokov"], + "_%n year ago_::_%n years ago_" : ["vlani","pred %n rokmi","pred %n rokmi","pred %n rokmi"], + "_in %n hour_::_in %n hours_" : ["o %n hodinu","o %n hodiny","o %n hodín","o %n hodín"], + "_%n hour ago_::_%n hours ago_" : ["pred %n hodinou","pred %n hodinami","pred %n hodinami","pred %n hodinami"], + "_in %n minute_::_in %n minutes_" : ["o %n minútu","o %n minúty","o %n minút","o %n minút"], + "_%n minute ago_::_%n minutes ago_" : ["pred %n minútou","pred %n minútami","pred %n minútami","pred %n minútami"], + "in a few seconds" : "o pár sekúnd", + "seconds ago" : "pred sekundami", + "Empty file" : "Prázdny súbor", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul s ID: %s neexistuje. Povoľte ho prosím vo vašom nastavení aplikácií alebo kontaktujte správcu.", + "File name is a reserved word" : "Názov súboru je rezervované slovo.", + "File name contains at least one invalid character" : "Názov súboru obsahuje nepovolené znaky.", + "File name is too long" : "Meno súboru je veľmi dlhé.", + "Dot files are not allowed" : "Názov súboru začínajúci bodkou nie je povolený.", + "Empty filename is not allowed" : "Prázdny názov súboru nie je povolený", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikáciu \"%s\" nie je možné nainštalovať, lebo nebolo možné načítať súbor s informáciami o aplikácií.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikácia \"%s\" nie je kompatibilná s verziou servera, preto nemôže byť nainštalovaná.", + "__language_name__" : "Slovenčina", + "This is an automatically sent email, please do not reply." : "Toto je automaticky odosielaný e-mail, prosím, neodpovedajte.", + "Help" : "Pomoc", + "Apps" : "Aplikácie", + "Settings" : "Nastavenia", + "Log out" : "Odhlásiť sa", + "Users" : "Používatelia", + "Unknown user" : "Neznámy používateľ", + "Additional settings" : "Ďalšie nastavenia", + "%s enter the database username and name." : "%s zadajte používateľské meno a meno databázy", + "%s enter the database username." : "Zadajte používateľské meno %s databázy.", + "%s enter the database name." : "Zadajte názov databázy pre %s databázy.", + "%s you may not use dots in the database name" : "V názve databázy %s nemôžete používať bodky", + "MySQL username and/or password not valid" : "Neplatné užívateľské meno a/alebo heslo do MySQL", + "You need to enter details of an existing account." : "Musíte zadať údaje existujúceho účtu.", + "Oracle connection could not be established" : "Nie je možné pripojiť sa k Oracle", + "Oracle username and/or password not valid" : "Používateľské meno a/alebo heslo pre Oracle databázu je neplatné", + "PostgreSQL username and/or password not valid" : "Používateľské meno a/alebo heslo pre PostgreSQL databázu je neplatné", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nie je podporovaný a %s nebude správne fungovať na tejto platforme. Použite ho na vlastné riziko!", + "For the best results, please consider using a GNU/Linux server instead." : "Pre dosiahnutie najlepších výsledkov, prosím zvážte použitie GNU/Linux servera.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Zdá sa, že táto inštancia %s beží v 32-bitovom prostredí PHP a v php.ini bola nastavená voľba open_basedir. To bude zdrojom problémov so súbormi väčšími ako 4GB a dôrazne sa neodporúča.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Prosím, odstráňte nastavenie open_basedir vo vašom php.ini alebo prejdite na 64-bit PHP.", + "Set an admin username." : "Zadajte používateľské meno administrátora.", + "Set an admin password." : "Zadajte heslo administrátora.", + "Can't create or write into the data directory %s" : "Nemožno vytvoriť alebo zapisovať do priečinka dát %s", + "Invalid Federated Cloud ID" : "Neplatné združené Cloud ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend pre sprístupnenie %s musí implementovať rozhranie OCP\\Share_Backend", + "Sharing backend %s not found" : "Backend sprístupnenia %s nebol nájdený", + "Sharing backend for %s not found" : "Backend sprístupnenia pre %s nebol nájdený", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s vám sprístupnil »%2$s« s poznámkou:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s vám sprístupnil »%2$s« s poznámkou", + "»%s« added a note to a file shared with you" : "»%s« pridal poznámku k súboru ktorý s Vami zdieľa", + "Open »%s«" : "Otvoriť »%s«", + "%1$s via %2$s" : "%1$s cez %2$s", + "You are not allowed to share %s" : "Nemôžete sprístupniť %s", + "Can’t increase permissions of %s" : "Nie je možné navýšiť oprávnenia pre %s", + "Files can’t be shared with delete permissions" : "Súbory nie je možné sprístupňovať s oprávneniami na odstránenie", + "Files can’t be shared with create permissions" : "Súbory nie je možné sprístupňovať s oprávneniami na vytváranie", + "Expiration date is in the past" : "Dátum konca platnosti je v minulosti", + "Can’t set expiration date more than %s days in the future" : "Nie je možné nastaviť dátum konca platnosti viac ako %s dní v budúcnosti", + "%1$s shared »%2$s« with you" : "%1$s vám sprístupnil »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s vám sprístupnil »%2$s«.", + "Click the button below to open it." : "Pre otvorenie klienta kliknite na tlačítko nižšie.", + "The requested share does not exist anymore" : "Požadované sprístupnenie už neexistuje", + "Could not find category \"%s\"" : "Nemožno nájsť danú kategóriu \"%s\"", + "Sunday" : "Nedeľa", + "Monday" : "Pondelok", + "Tuesday" : "Utorok", + "Wednesday" : "Streda", + "Thursday" : "Štvrtok", + "Friday" : "Piatok", + "Saturday" : "Sobota", + "Sun." : "Ned.", + "Mon." : "Pon.", + "Tue." : "Uto.", + "Wed." : "Str.", + "Thu." : "Štv.", + "Fri." : "Pia.", + "Sat." : "Sob.", + "Su" : "Ne", + "Mo" : "Po", + "Tu" : "Ut", + "We" : "St", + "Th" : "Št", + "Fr" : "Pi", + "Sa" : "So", + "January" : "Január", + "February" : "Február", + "March" : "Marec", + "April" : "Apríl", + "May" : "Máj", + "June" : "Jún", + "July" : "Júl", + "August" : "August", + "September" : "September", + "October" : "Október", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Máj.", + "Jun." : "Jún.", + "Jul." : "Júl.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V mene používateľa je možné použiť iba nasledovné znaky: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Musíte zadať platné používateľské meno", + "Username contains whitespace at the beginning or at the end" : "Meno používateľa obsahuje na začiatku, alebo na konci medzeru", + "Username must not consist of dots only" : "Používateľské meno sa nesmie skladať len z bodiek", + "Username is invalid because files already exist for this user" : "Používateľské meno je neplatné, pretože pre tohto používateľa už existujú súbory", + "A valid password must be provided" : "Musíte zadať platné heslo", + "The username is already being used" : "Meno používateľa je už použité", + "Could not create user" : "Nepodarilo sa vytvoriť používateľa", + "User disabled" : "Používateľ zakázaný", + "Login canceled by app" : "Prihlásenie bolo zrušené aplikáciou", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplikáciu \"%1$s\" nie je možné inštalovať, pretože nie sú splnené nasledovné závislosti: %2$s", + "a safe home for all your data" : "bezpečný domov pre všetky vaše dáta", + "File is currently busy, please try again later" : "Súbor sa práve používa, skúste prosím neskôr", + "Can't read file" : "Nemožno čítať súbor.", + "Application is not enabled" : "Aplikácia nie je zapnutá", + "Authentication error" : "Chyba autentifikácie", + "Token expired. Please reload page." : "Token vypršal. Obnovte, prosím, stránku.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ovládače databázy (sqlite, mysql, alebo postgresql) nie sú nainštalované.", + "Cannot write into \"config\" directory" : "Nie je možné zapisovať do priečinka \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do adresára s konfiguráciou. Viď %s", + "Cannot write into \"apps\" directory" : "Nie je možné zapisovať do priečinka \"apps\"", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Toto sa zvyčajne dá opraviť poskytnutím práva webového servera na zápis do priečinka s aplikáciami alebo vypnutím katalógu s aplikáciami v súbore s nastaveniami.", + "Cannot create \"data\" directory" : "Nie je možné vytvoriť priečinok \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "To je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do koreňového adresára. Viď %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Oprávnenia je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do koreňového priečinka. Viď %s.", + "Setting locale to %s failed" : "Nastavenie locale na %s zlyhalo", + "Please install one of these locales on your system and restart your webserver." : "Prosím, nainštalujte si aspoň jeden z týchto jazykov so svojho systému a reštartujte webserver.", + "PHP module %s not installed." : "PHP modul %s nie je nainštalovaný.", + "Please ask your server administrator to install the module." : "Prosím, požiadajte administrátora vášho servera o inštaláciu modulu.", + "PHP setting \"%s\" is not set to \"%s\"." : "Voľba PHP „%s“ nie je nastavená na „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Použitím týchto nastavení v php.ini dovolí Nextcloudu sa znova spustiť", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je nastavený na \"%s\", namiesto predpokladanej hodnoty \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Oprava problému spočíva v nastavení mbstring.func_overload na 0 vo vašom php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Vyžadovaná verzia libxml2 je 2.7.0 a vyššia. Momentálne je nainštalovaná verzia %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Pre vyriešenie tohto problému aktualizujte prosím verziu libxml2 a reštartujte webový server.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je zjavne nastavené, aby odstraňovalo bloky vloženej dokumentácie. To zneprístupní niekoľko základných aplikácií.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "To je pravdepodobne spôsobené cache/akcelerátorom ako napr. Zend OPcache alebo eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly boli nainštalované, ale stále sa tvária, že chýbajú?", + "Please ask your server administrator to restart the web server." : "Prosím, požiadajte administrátora vášho servera o reštartovanie webového servera.", + "PostgreSQL >= 9 required" : "Vyžadované PostgreSQL >= 9", + "Please upgrade your database version" : "Prosím, aktualizujte verziu svojej databázy", + "Your data directory is readable by other users" : "Váš priečinok s dátami je prístupný na čítanie ostatným používateľom", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Prosím, zmeňte oprávnenia na 0770, aby tento priečinok nemohli ostatní používatelia otvoriť.", + "Your data directory must be an absolute path" : "Priečinok s dátami musí byť zadaný ako absolútna cesta", + "Check the value of \"datadirectory\" in your configuration" : "Skontrolujte hodnotu \"datadirectory\" vo vašej konfigurácii", + "Your data directory is invalid" : "Priečinok s dátami je neplatný", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Uistite sa, že v koreňovom adresári sa nachádza súbor s názvom \".ocdata\".", + "Action \"%s\" not supported or implemented." : "Akcia \"%s\" nie je podporovaná alebo implementovaná.", + "Authentication failed, wrong token or provider ID given" : "Autentifikácia zlyhala, bol zadaný neplatný token alebo ID poskytovateľa", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Pre dokončenie požiadavky chýbajú parametre. Chýbajúce parametre: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" je už použité v združenom cloude \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Poskytovateľ združeného cloudu s ID: \"%s\" neexistuje.", + "Could not obtain lock type %d on \"%s\"." : "Nepodarilo sa získať zámok typu %d na „%s“.", + "Storage unauthorized. %s" : "Úložisko neoverené. %s", + "Storage incomplete configuration. %s" : "Neplatná konfigurácia úložiska. %s", + "Storage connection error. %s" : "Chyba pripojenia k úložisku. %s", + "Storage is temporarily not available" : "Úložisko je dočasne nedostupné", + "Storage connection timeout. %s" : "Vypršanie pripojenia k úložisku. %s", + "Following databases are supported: %s" : "Podporované sú tieto databázy: %s", + "Following platforms are supported: %s" : "Podporované sú nasledovné systémy: %s", + "Overview" : "Prehľad", + "Basic settings" : "Základné nastavenia", + "Sharing" : "Sprístupnenie", + "Security" : "Zabezpečenie", + "Groupware" : "Groupware", + "Personal info" : "Osobné informácie", + "Mobile & desktop" : "Mobil a počítač", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Toto je zvyčajne možné opraviť tým, že udelíte webovému serveru oprávnenie na zápis do priečinka aplikácií alebo vypnete obchod s aplikáciami v konfiguračnom súbore. Viď %s" +},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sl.js b/docker/overlays/nextcloud/html/lib/l10n/sl.js new file mode 100644 index 0000000..967b146 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sl.js @@ -0,0 +1,224 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "V mapo »config« ni mogoče zapisovati!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo.", + "See %s" : "Oglejte si %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke programa %1$s niso bile zamenjane na pravi način. Prepričajte se, da je na strežniku nameščena podprta različica.", + "Sample configuration detected" : "Zaznana je neustrezna vzorčna nastavitev", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "V sistem je bila kopirana datoteka s vzorčnimi nastavitvami. To lahko vpliva na namestitev in zato možnost ni podprta. Pred spremembami datoteke config.php si natančno preberite dokumentacijo.", + "Other activities" : "Druge dejavnosti", + "%1$s and %2$s" : "%1$s in %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s in %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s in %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s in %5$s", + "Education Edition" : "Izobraževalna različica", + "Enterprise bundle" : "Poslovni paket", + "Groupware bundle" : "Paket za skupinsko delo", + "Social sharing bundle" : "Paket družbene izmenjave", + "PHP %s or higher is required." : "Zahtevana je različica PHP %s ali višja.", + "PHP with a version lower than %s is required." : "Zahtevana je različica PHP nižja od %s.", + "%sbit or higher PHP required." : "Zahtevana je %s-bitna različica PHP višja.", + "The following databases are supported: %s" : "Podprte so navedene podatkovne zbirke: %s", + "The command line tool %s could not be found" : "Orodja ukazne vrstice %s ni mogoče najti", + "The library %s is not available." : "Knjižnica %s ni na voljo.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Zahtevana je knjižnica %1$s z različico, višjo od %2$s – na voljo je različica %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Zahtevana je knjižnica %1$s z različico, manjšo od %2$s – na voljo je različica %3$s.", + "The following platforms are supported: %s" : "Podprta so okolja: %s", + "Server version %s or higher is required." : "Zahtevana je različica strežnika %s ali višja.", + "Server version %s or lower is required." : "Zahtevana je različica strežnika %s ali nižja.", + "Logged in user must be an admin or sub admin" : "Prijavljen uporabnik mora imeti dovoljenja skrbnika ali podpornega skrbnika", + "Logged in user must be an admin" : "Prijavljen uporabnik mora biti tudi skrbnik", + "Wiping of device %s has started" : "Začeto je brisanje podatkov na napravi %s.", + "Wiping of device »%s« has started" : "Začeto je brisanje podatkov na napravi »%s«", + "Wiping of device %s has finished" : "Brisanje podatkov na napravi %s je končano.", + "Wiping of device »%s« has finished" : "Brisanje podatkov na napravi »%s« je končano.", + "Remote wipe started" : "Začeto je oddaljeno varnostno brisanje", + "A remote wipe was started on device %s" : "Začeto je bilo oddaljeno varnostno brisanje na napravi %s.", + "Remote wipe finished" : "Oddaljeno varnostno brisanje je končano", + "The remote wipe on %s has finished" : "Oddaljeno varnostno brisanje na %s je končano", + "Authentication" : "Overitev", + "Unknown filetype" : "Neznana vrsta datoteke", + "Invalid image" : "Neveljavna slika", + "Avatar image is not square" : "Slika podobe ni kvadratna", + "today" : "danes", + "tomorrow" : "jutri", + "yesterday" : "včeraj", + "_in %n day_::_in %n days_" : ["čez %n dan","čez %n dneva","čez %n dni","čez %n dni"], + "_%n day ago_::_%n days ago_" : ["pred %n dnevom","pred %n dnevoma","pred %n dnevi","pred %n dnevi"], + "next month" : "naslednji mesec", + "last month" : "zadnji mesec", + "_in %n month_::_in %n months_" : ["čez %n mesec","čez %n meseca","čez %n mesece","čez %n mesecev"], + "_%n month ago_::_%n months ago_" : ["pred %n mesecem","pred %n mesecema","pred %n meseci","pred %n meseci"], + "next year" : "naslednje leto", + "last year" : "lansko leto", + "_in %n year_::_in %n years_" : ["čez %n leto","čez %n leti","čez %n leta","čez %n let"], + "_%n year ago_::_%n years ago_" : ["pred %n letom","pred %n letoma","pred %n leti","pred %n leti"], + "_in %n hour_::_in %n hours_" : ["čez %n uro","čez %n uri","čez %n ure","čez %n ur"], + "_%n hour ago_::_%n hours ago_" : ["pred %n uro","pred %n urama","pred %n urami","pred %n urami"], + "_in %n minute_::_in %n minutes_" : ["čez %n minuto","čez %n minuti","čez %n minute","čez %n minut"], + "_%n minute ago_::_%n minutes ago_" : ["pred %n minuto","pred %n minutama","pred %n minutami","pred %n minutami"], + "in a few seconds" : "čez nekaj sekund", + "seconds ago" : "pred nekaj sekundami", + "Empty file" : "Prazna datoteka", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul z ID: %s ne obstaja. Omogočite ga med nastavitvami, ali pa stopite v stik s skrbnikom sistema.", + "File name is a reserved word" : "Ime datoteke je zadržana beseda", + "File name contains at least one invalid character" : "Ime datoteke vsebuje vsaj en nedovoljen znak.", + "File name is too long" : "Ime datoteke je predolgo", + "Dot files are not allowed" : "Skrite datoteke niso dovoljene", + "Empty filename is not allowed" : "Prazno polje imena datoteke ni dovoljeno.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Programa »%s« ni mogoče namestiti, ker datoteke appinfo ni mogoče brati.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Programa »%s« ni mogoče namestiti, ker ni združljiv z nameščeno različico strežnika.", + "__language_name__" : "Slovenščina", + "This is an automatically sent email, please do not reply." : "To sporočilo je samodejno poslano, nanj se nima smisla odzvati.", + "Help" : "Pomoč", + "Apps" : "Programi", + "Settings" : "Nastavitve", + "Log out" : "Odjava", + "Users" : "Uporabniki", + "Unknown user" : "Neznan uporabnik", + "Additional settings" : "Dodatne nastavitve", + "%s enter the database username and name." : "%s – vnos uporabniškega imena in imena podatkovne zbirke.", + "%s enter the database username." : "%s – vnos uporabniškega imena podatkovne zbirke.", + "%s enter the database name." : "%s – vnos imena podatkovne zbirke.", + "%s you may not use dots in the database name" : "%s – v imenu podatkovne zbirke ni dovoljeno uporabljati pik.", + "MySQL username and/or password not valid" : "Uporabniško ime ali geslo MySQL ni veljavno", + "You need to enter details of an existing account." : "Vpisati je treba podrobnosti obstoječega računa.", + "Oracle connection could not be established" : "Povezave s sistemom Oracle ni mogoče vzpostaviti.", + "Oracle username and/or password not valid" : "Uporabniško ime ali geslo Oracle ni veljavno", + "PostgreSQL username and/or password not valid" : "Uporabniško ime ali geslo PostgreSQL ni veljavno", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Sistem Mac OS X ni podprt, zato %s v tem okolju ne bo deloval zanesljivo. Program uporabljate na lastno odgovornost! ", + "For the best results, please consider using a GNU/Linux server instead." : "Za najboljše rezultate je priporočljivo uporabljati strežnik GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Kaže, da je dejavna seja %s zagnana v 32-bitnem okolju PHP in, da je v datoteki php.ini nastavljen open_basedir . Tako delovanje ni priporočljivo, saj se lahko pojavijo težave z datotekami, večjimi od 4GB.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Odstraniti je treba nastavitev open_basedir v datoteki php.ini ali pa preklopiti na 64-bitno okolje PHP.", + "Set an admin username." : "Nastavi uporabniško ime skrbnika.", + "Set an admin password." : "Nastavi skrbniško geslo.", + "Can't create or write into the data directory %s" : "Ni mogoče zapisati podatkov v podatkovno mapo %s", + "Invalid Federated Cloud ID" : "Neveljaven ID zveznega oblaka", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Souporaba ozadnjega programa %s mora vsebovati tudi vmesnik OCP\\Share_Backend", + "Sharing backend %s not found" : "Ozadnjega programa %s za souporabo ni mogoče najti", + "Sharing backend for %s not found" : "Ozadnjega programa za souporabo za %s ni mogoče najti", + "»%s« added a note to a file shared with you" : "»%s« doda opombo k datoteki v souporabi", + "Open »%s«" : "Odpri »%s«", + "%1$s via %2$s" : "%1$s prek %2$s", + "You are not allowed to share %s" : "Omogočanje souporabe %s brez ustreznih dovoljenj ni mogoče.", + "Can’t increase permissions of %s" : "Ni mogoče povečati dovoljen %s", + "Files can’t be shared with delete permissions" : "Souporaba datotek z nastavljenim dovoljenjem za brisanje, ni mogoča", + "Files can’t be shared with create permissions" : "Souporaba datotek z nastavljenim dovoljenjem za ustvarjanje, ni mogoča", + "Expiration date is in the past" : "Datum preteka je že mimo!", + "Can’t set expiration date more than %s days in the future" : "Datuma pretaka ni mogoče nastaviti za več kot %s dni v prihodnost.", + "%1$s shared »%2$s« with you" : "%1$s vam omogoča souporabo »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s vam omogoča souporabo »%2$s«.", + "Click the button below to open it." : "Kliknite na gumb za odpiranje.", + "The requested share does not exist anymore" : "Zahtevano mesto souporabe ne obstaja več", + "Could not find category \"%s\"" : "Kategorije »%s« ni mogoče najti.", + "Sunday" : "nedelja", + "Monday" : "ponedeljek", + "Tuesday" : "torek", + "Wednesday" : "sreda", + "Thursday" : "četrtek", + "Friday" : "petek", + "Saturday" : "sobota", + "Sun." : "ned", + "Mon." : "pon", + "Tue." : "tor", + "Wed." : "sre", + "Thu." : "čet", + "Fri." : "pet", + "Sat." : "sob", + "Su" : "ne", + "Mo" : "po", + "Tu" : "to", + "We" : "sr", + "Th" : "če", + "Fr" : "pe", + "Sa" : "so", + "January" : "januar", + "February" : "februar", + "March" : "marec", + "April" : "april", + "May" : "maj", + "June" : "junij", + "July" : "julij", + "August" : "avgust", + "September" : "september", + "October" : "oktober", + "November" : "november", + "December" : "december", + "Jan." : "jan", + "Feb." : "feb", + "Mar." : "mar", + "Apr." : "apr", + "May." : "maj", + "Jun." : "jun", + "Jul." : "jul", + "Aug." : "avg", + "Sep." : "sep", + "Oct." : "okt", + "Nov." : "nov", + "Dec." : "dec", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V uporabniškem imenu je dovoljeno uporabiti le znake: »a–z«, »A–Z«, »0–9« in »_.@-«\".", + "A valid username must be provided" : "Navedeno mora biti veljavno uporabniško ime", + "Username contains whitespace at the beginning or at the end" : "Uporabniško ime vsebuje presledni znak na začetku ali na koncu imena.", + "Username must not consist of dots only" : "Uporabniško ime ne sme biti zgolj iz pik", + "Username is invalid because files already exist for this user" : "Uporabniško ime ni veljavno, ker za tega uporabnika že obstajajo datoteke", + "A valid password must be provided" : "Navedeno mora biti veljavno geslo", + "The username is already being used" : "Vpisano uporabniško ime je že v uporabi", + "Could not create user" : "Uporabnika ni mogoče ustvariti", + "User disabled" : "Uporabnik je onemogočen", + "Login canceled by app" : "Program onemogoča prijavo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programa »%1$s« ni mogoče namestiti zaradi nerešenih odvisnosti: %2$s", + "a safe home for all your data" : "Varno okolje za vaše podatke!", + "File is currently busy, please try again later" : "Datoteka je trenutno v uporabi. Poskusite znova kasneje.", + "Can't read file" : "Datoteke ni mogoče prebrati.", + "Application is not enabled" : "Program ni omogočen", + "Authentication error" : "Napaka overjanja", + "Token expired. Please reload page." : "Žeton je potekel. Stran je treba ponovno naložiti.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ni nameščenih programnikov podatkovnih zbirk (sqlite, mysql oziroma postgresql).", + "Cannot write into \"config\" directory" : "Mapa »config« nima nastavljenih ustreznih dovoljenj za pisanje!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo. Poglejte %s", + "Cannot write into \"apps\" directory" : "V mapo »apps« ni mogoče zapisovati!", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v mapo programov, ali pa z onemogočanjem zborle programov v nastavitveni datoteki.", + "Cannot create \"data\" directory" : "Ni mogoče ustvariti »podatkovne« mape", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v korensko mapo. Poglejte %s", + "Setting locale to %s failed" : "Nastavljanje jezikovnih določil na %s je spodletelo.", + "Please install one of these locales on your system and restart your webserver." : "Namestiti je treba podporo za vsaj eno od navedenih jezikovnih določil v sistemu in nato ponovno zagnati spletni strežnik.", + "PHP module %s not installed." : "Modul PHP %s ni nameščen.", + "Please ask your server administrator to install the module." : "Obvestite skrbnika strežnika, da je treba namestiti manjkajoč modul.", + "PHP setting \"%s\" is not set to \"%s\"." : "Nastavitev PHP »%s« ni nastavljena na »%s«.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Prilagoditev te nastavitve v php.ini vzpostavi ponovno delovanje okolja NextCloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "Vrednost mbstring.func_overload je nastavljena na »%s« in ne na pričakovano vrednost »0«.", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Za rešitev težave je treba v datoteki php.ini nastaviti možnost mbstring.func_overload na 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Različica knjižnice libxml2 mora biti 2.7.0 ali višja. Trenutno je nameščena %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : " Kaže, da je PHP nastavljen tako, da odreže vrstične predmete docblock. To povzroči, da nekateri jedrni programi niso dosegljivi.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Napako je najverjetneje povzročil predpomnilnik ali pospeševalnik, kot sta Zend OPcache ali eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Ali so bili moduli PHP nameščeni, pa so še vedno označeni kot manjkajoči?", + "Please ask your server administrator to restart the web server." : "Obvestite skrbnika strežnika, da je treba ponovno zagnati spletni strežnik.", + "PostgreSQL >= 9 required" : "Zahtevana je različica PostgreSQL >= 9.", + "Please upgrade your database version" : "Posodobite različico podatkovne zbirke.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Spremenite dovoljenja mape na 0770 in s tem onemogočite branje vsebine drugim uporabnikom.", + "Your data directory must be an absolute path" : "Podatkovna mapa mora imeti navedeno celotno pot.", + "Check the value of \"datadirectory\" in your configuration" : "Med nastavitvami je treba preveriti nastavitev »datadirectory«", + "Your data directory is invalid" : "Podatkovna mapa ni veljavna.", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Prepričajte se, da je datoteka ».ocdata« v korenu podatkovne mape.", + "Action \"%s\" not supported or implemented." : "Dejanje »%s« ni podprto ali omogočeno.", + "Authentication failed, wrong token or provider ID given" : "Overitev je spodletela, podan je napačen žeton oziroma določilo ID ponudnika", + "Could not obtain lock type %d on \"%s\"." : "Ni mogoče pridobiti zaklepa %d na \"%s\".", + "Storage unauthorized. %s" : "Dostop do shrambe ni overjen. %s", + "Storage incomplete configuration. %s" : "Nepopolna nastavitev shrambe. %s", + "Storage connection error. %s" : "Napaka povezave do shrambe. %s", + "Storage is temporarily not available" : "Shramba trenutno ni na voljo", + "Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s", + "Following databases are supported: %s" : "Podprte so navedene podatkovne zbirke: %s", + "Following platforms are supported: %s" : "Podprta so okolja: %s", + "Overview" : "Splošni pregled", + "Basic settings" : "Osnovne nastavitve", + "Sharing" : "Souporaba", + "Security" : "Varnost", + "Groupware" : "Skupinsko delo", + "Personal info" : "Osebni podatki", + "Mobile & desktop" : "Mobilni in namizni dostop" +}, +"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sl.json b/docker/overlays/nextcloud/html/lib/l10n/sl.json new file mode 100644 index 0000000..4c4c20f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sl.json @@ -0,0 +1,222 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "V mapo »config« ni mogoče zapisovati!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo.", + "See %s" : "Oglejte si %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo.", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke programa %1$s niso bile zamenjane na pravi način. Prepričajte se, da je na strežniku nameščena podprta različica.", + "Sample configuration detected" : "Zaznana je neustrezna vzorčna nastavitev", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "V sistem je bila kopirana datoteka s vzorčnimi nastavitvami. To lahko vpliva na namestitev in zato možnost ni podprta. Pred spremembami datoteke config.php si natančno preberite dokumentacijo.", + "Other activities" : "Druge dejavnosti", + "%1$s and %2$s" : "%1$s in %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s in %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s in %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s in %5$s", + "Education Edition" : "Izobraževalna različica", + "Enterprise bundle" : "Poslovni paket", + "Groupware bundle" : "Paket za skupinsko delo", + "Social sharing bundle" : "Paket družbene izmenjave", + "PHP %s or higher is required." : "Zahtevana je različica PHP %s ali višja.", + "PHP with a version lower than %s is required." : "Zahtevana je različica PHP nižja od %s.", + "%sbit or higher PHP required." : "Zahtevana je %s-bitna različica PHP višja.", + "The following databases are supported: %s" : "Podprte so navedene podatkovne zbirke: %s", + "The command line tool %s could not be found" : "Orodja ukazne vrstice %s ni mogoče najti", + "The library %s is not available." : "Knjižnica %s ni na voljo.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Zahtevana je knjižnica %1$s z različico, višjo od %2$s – na voljo je različica %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Zahtevana je knjižnica %1$s z različico, manjšo od %2$s – na voljo je različica %3$s.", + "The following platforms are supported: %s" : "Podprta so okolja: %s", + "Server version %s or higher is required." : "Zahtevana je različica strežnika %s ali višja.", + "Server version %s or lower is required." : "Zahtevana je različica strežnika %s ali nižja.", + "Logged in user must be an admin or sub admin" : "Prijavljen uporabnik mora imeti dovoljenja skrbnika ali podpornega skrbnika", + "Logged in user must be an admin" : "Prijavljen uporabnik mora biti tudi skrbnik", + "Wiping of device %s has started" : "Začeto je brisanje podatkov na napravi %s.", + "Wiping of device »%s« has started" : "Začeto je brisanje podatkov na napravi »%s«", + "Wiping of device %s has finished" : "Brisanje podatkov na napravi %s je končano.", + "Wiping of device »%s« has finished" : "Brisanje podatkov na napravi »%s« je končano.", + "Remote wipe started" : "Začeto je oddaljeno varnostno brisanje", + "A remote wipe was started on device %s" : "Začeto je bilo oddaljeno varnostno brisanje na napravi %s.", + "Remote wipe finished" : "Oddaljeno varnostno brisanje je končano", + "The remote wipe on %s has finished" : "Oddaljeno varnostno brisanje na %s je končano", + "Authentication" : "Overitev", + "Unknown filetype" : "Neznana vrsta datoteke", + "Invalid image" : "Neveljavna slika", + "Avatar image is not square" : "Slika podobe ni kvadratna", + "today" : "danes", + "tomorrow" : "jutri", + "yesterday" : "včeraj", + "_in %n day_::_in %n days_" : ["čez %n dan","čez %n dneva","čez %n dni","čez %n dni"], + "_%n day ago_::_%n days ago_" : ["pred %n dnevom","pred %n dnevoma","pred %n dnevi","pred %n dnevi"], + "next month" : "naslednji mesec", + "last month" : "zadnji mesec", + "_in %n month_::_in %n months_" : ["čez %n mesec","čez %n meseca","čez %n mesece","čez %n mesecev"], + "_%n month ago_::_%n months ago_" : ["pred %n mesecem","pred %n mesecema","pred %n meseci","pred %n meseci"], + "next year" : "naslednje leto", + "last year" : "lansko leto", + "_in %n year_::_in %n years_" : ["čez %n leto","čez %n leti","čez %n leta","čez %n let"], + "_%n year ago_::_%n years ago_" : ["pred %n letom","pred %n letoma","pred %n leti","pred %n leti"], + "_in %n hour_::_in %n hours_" : ["čez %n uro","čez %n uri","čez %n ure","čez %n ur"], + "_%n hour ago_::_%n hours ago_" : ["pred %n uro","pred %n urama","pred %n urami","pred %n urami"], + "_in %n minute_::_in %n minutes_" : ["čez %n minuto","čez %n minuti","čez %n minute","čez %n minut"], + "_%n minute ago_::_%n minutes ago_" : ["pred %n minuto","pred %n minutama","pred %n minutami","pred %n minutami"], + "in a few seconds" : "čez nekaj sekund", + "seconds ago" : "pred nekaj sekundami", + "Empty file" : "Prazna datoteka", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul z ID: %s ne obstaja. Omogočite ga med nastavitvami, ali pa stopite v stik s skrbnikom sistema.", + "File name is a reserved word" : "Ime datoteke je zadržana beseda", + "File name contains at least one invalid character" : "Ime datoteke vsebuje vsaj en nedovoljen znak.", + "File name is too long" : "Ime datoteke je predolgo", + "Dot files are not allowed" : "Skrite datoteke niso dovoljene", + "Empty filename is not allowed" : "Prazno polje imena datoteke ni dovoljeno.", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Programa »%s« ni mogoče namestiti, ker datoteke appinfo ni mogoče brati.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Programa »%s« ni mogoče namestiti, ker ni združljiv z nameščeno različico strežnika.", + "__language_name__" : "Slovenščina", + "This is an automatically sent email, please do not reply." : "To sporočilo je samodejno poslano, nanj se nima smisla odzvati.", + "Help" : "Pomoč", + "Apps" : "Programi", + "Settings" : "Nastavitve", + "Log out" : "Odjava", + "Users" : "Uporabniki", + "Unknown user" : "Neznan uporabnik", + "Additional settings" : "Dodatne nastavitve", + "%s enter the database username and name." : "%s – vnos uporabniškega imena in imena podatkovne zbirke.", + "%s enter the database username." : "%s – vnos uporabniškega imena podatkovne zbirke.", + "%s enter the database name." : "%s – vnos imena podatkovne zbirke.", + "%s you may not use dots in the database name" : "%s – v imenu podatkovne zbirke ni dovoljeno uporabljati pik.", + "MySQL username and/or password not valid" : "Uporabniško ime ali geslo MySQL ni veljavno", + "You need to enter details of an existing account." : "Vpisati je treba podrobnosti obstoječega računa.", + "Oracle connection could not be established" : "Povezave s sistemom Oracle ni mogoče vzpostaviti.", + "Oracle username and/or password not valid" : "Uporabniško ime ali geslo Oracle ni veljavno", + "PostgreSQL username and/or password not valid" : "Uporabniško ime ali geslo PostgreSQL ni veljavno", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Sistem Mac OS X ni podprt, zato %s v tem okolju ne bo deloval zanesljivo. Program uporabljate na lastno odgovornost! ", + "For the best results, please consider using a GNU/Linux server instead." : "Za najboljše rezultate je priporočljivo uporabljati strežnik GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Kaže, da je dejavna seja %s zagnana v 32-bitnem okolju PHP in, da je v datoteki php.ini nastavljen open_basedir . Tako delovanje ni priporočljivo, saj se lahko pojavijo težave z datotekami, večjimi od 4GB.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Odstraniti je treba nastavitev open_basedir v datoteki php.ini ali pa preklopiti na 64-bitno okolje PHP.", + "Set an admin username." : "Nastavi uporabniško ime skrbnika.", + "Set an admin password." : "Nastavi skrbniško geslo.", + "Can't create or write into the data directory %s" : "Ni mogoče zapisati podatkov v podatkovno mapo %s", + "Invalid Federated Cloud ID" : "Neveljaven ID zveznega oblaka", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Souporaba ozadnjega programa %s mora vsebovati tudi vmesnik OCP\\Share_Backend", + "Sharing backend %s not found" : "Ozadnjega programa %s za souporabo ni mogoče najti", + "Sharing backend for %s not found" : "Ozadnjega programa za souporabo za %s ni mogoče najti", + "»%s« added a note to a file shared with you" : "»%s« doda opombo k datoteki v souporabi", + "Open »%s«" : "Odpri »%s«", + "%1$s via %2$s" : "%1$s prek %2$s", + "You are not allowed to share %s" : "Omogočanje souporabe %s brez ustreznih dovoljenj ni mogoče.", + "Can’t increase permissions of %s" : "Ni mogoče povečati dovoljen %s", + "Files can’t be shared with delete permissions" : "Souporaba datotek z nastavljenim dovoljenjem za brisanje, ni mogoča", + "Files can’t be shared with create permissions" : "Souporaba datotek z nastavljenim dovoljenjem za ustvarjanje, ni mogoča", + "Expiration date is in the past" : "Datum preteka je že mimo!", + "Can’t set expiration date more than %s days in the future" : "Datuma pretaka ni mogoče nastaviti za več kot %s dni v prihodnost.", + "%1$s shared »%2$s« with you" : "%1$s vam omogoča souporabo »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s vam omogoča souporabo »%2$s«.", + "Click the button below to open it." : "Kliknite na gumb za odpiranje.", + "The requested share does not exist anymore" : "Zahtevano mesto souporabe ne obstaja več", + "Could not find category \"%s\"" : "Kategorije »%s« ni mogoče najti.", + "Sunday" : "nedelja", + "Monday" : "ponedeljek", + "Tuesday" : "torek", + "Wednesday" : "sreda", + "Thursday" : "četrtek", + "Friday" : "petek", + "Saturday" : "sobota", + "Sun." : "ned", + "Mon." : "pon", + "Tue." : "tor", + "Wed." : "sre", + "Thu." : "čet", + "Fri." : "pet", + "Sat." : "sob", + "Su" : "ne", + "Mo" : "po", + "Tu" : "to", + "We" : "sr", + "Th" : "če", + "Fr" : "pe", + "Sa" : "so", + "January" : "januar", + "February" : "februar", + "March" : "marec", + "April" : "april", + "May" : "maj", + "June" : "junij", + "July" : "julij", + "August" : "avgust", + "September" : "september", + "October" : "oktober", + "November" : "november", + "December" : "december", + "Jan." : "jan", + "Feb." : "feb", + "Mar." : "mar", + "Apr." : "apr", + "May." : "maj", + "Jun." : "jun", + "Jul." : "jul", + "Aug." : "avg", + "Sep." : "sep", + "Oct." : "okt", + "Nov." : "nov", + "Dec." : "dec", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V uporabniškem imenu je dovoljeno uporabiti le znake: »a–z«, »A–Z«, »0–9« in »_.@-«\".", + "A valid username must be provided" : "Navedeno mora biti veljavno uporabniško ime", + "Username contains whitespace at the beginning or at the end" : "Uporabniško ime vsebuje presledni znak na začetku ali na koncu imena.", + "Username must not consist of dots only" : "Uporabniško ime ne sme biti zgolj iz pik", + "Username is invalid because files already exist for this user" : "Uporabniško ime ni veljavno, ker za tega uporabnika že obstajajo datoteke", + "A valid password must be provided" : "Navedeno mora biti veljavno geslo", + "The username is already being used" : "Vpisano uporabniško ime je že v uporabi", + "Could not create user" : "Uporabnika ni mogoče ustvariti", + "User disabled" : "Uporabnik je onemogočen", + "Login canceled by app" : "Program onemogoča prijavo", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programa »%1$s« ni mogoče namestiti zaradi nerešenih odvisnosti: %2$s", + "a safe home for all your data" : "Varno okolje za vaše podatke!", + "File is currently busy, please try again later" : "Datoteka je trenutno v uporabi. Poskusite znova kasneje.", + "Can't read file" : "Datoteke ni mogoče prebrati.", + "Application is not enabled" : "Program ni omogočen", + "Authentication error" : "Napaka overjanja", + "Token expired. Please reload page." : "Žeton je potekel. Stran je treba ponovno naložiti.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Ni nameščenih programnikov podatkovnih zbirk (sqlite, mysql oziroma postgresql).", + "Cannot write into \"config\" directory" : "Mapa »config« nima nastavljenih ustreznih dovoljenj za pisanje!", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo. Poglejte %s", + "Cannot write into \"apps\" directory" : "V mapo »apps« ni mogoče zapisovati!", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v mapo programov, ali pa z onemogočanjem zborle programov v nastavitveni datoteki.", + "Cannot create \"data\" directory" : "Ni mogoče ustvariti »podatkovne« mape", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v korensko mapo. Poglejte %s", + "Setting locale to %s failed" : "Nastavljanje jezikovnih določil na %s je spodletelo.", + "Please install one of these locales on your system and restart your webserver." : "Namestiti je treba podporo za vsaj eno od navedenih jezikovnih določil v sistemu in nato ponovno zagnati spletni strežnik.", + "PHP module %s not installed." : "Modul PHP %s ni nameščen.", + "Please ask your server administrator to install the module." : "Obvestite skrbnika strežnika, da je treba namestiti manjkajoč modul.", + "PHP setting \"%s\" is not set to \"%s\"." : "Nastavitev PHP »%s« ni nastavljena na »%s«.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Prilagoditev te nastavitve v php.ini vzpostavi ponovno delovanje okolja NextCloud", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "Vrednost mbstring.func_overload je nastavljena na »%s« in ne na pričakovano vrednost »0«.", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Za rešitev težave je treba v datoteki php.ini nastaviti možnost mbstring.func_overload na 0.", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Različica knjižnice libxml2 mora biti 2.7.0 ali višja. Trenutno je nameščena %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : " Kaže, da je PHP nastavljen tako, da odreže vrstične predmete docblock. To povzroči, da nekateri jedrni programi niso dosegljivi.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Napako je najverjetneje povzročil predpomnilnik ali pospeševalnik, kot sta Zend OPcache ali eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Ali so bili moduli PHP nameščeni, pa so še vedno označeni kot manjkajoči?", + "Please ask your server administrator to restart the web server." : "Obvestite skrbnika strežnika, da je treba ponovno zagnati spletni strežnik.", + "PostgreSQL >= 9 required" : "Zahtevana je različica PostgreSQL >= 9.", + "Please upgrade your database version" : "Posodobite različico podatkovne zbirke.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Spremenite dovoljenja mape na 0770 in s tem onemogočite branje vsebine drugim uporabnikom.", + "Your data directory must be an absolute path" : "Podatkovna mapa mora imeti navedeno celotno pot.", + "Check the value of \"datadirectory\" in your configuration" : "Med nastavitvami je treba preveriti nastavitev »datadirectory«", + "Your data directory is invalid" : "Podatkovna mapa ni veljavna.", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Prepričajte se, da je datoteka ».ocdata« v korenu podatkovne mape.", + "Action \"%s\" not supported or implemented." : "Dejanje »%s« ni podprto ali omogočeno.", + "Authentication failed, wrong token or provider ID given" : "Overitev je spodletela, podan je napačen žeton oziroma določilo ID ponudnika", + "Could not obtain lock type %d on \"%s\"." : "Ni mogoče pridobiti zaklepa %d na \"%s\".", + "Storage unauthorized. %s" : "Dostop do shrambe ni overjen. %s", + "Storage incomplete configuration. %s" : "Nepopolna nastavitev shrambe. %s", + "Storage connection error. %s" : "Napaka povezave do shrambe. %s", + "Storage is temporarily not available" : "Shramba trenutno ni na voljo", + "Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s", + "Following databases are supported: %s" : "Podprte so navedene podatkovne zbirke: %s", + "Following platforms are supported: %s" : "Podprta so okolja: %s", + "Overview" : "Splošni pregled", + "Basic settings" : "Osnovne nastavitve", + "Sharing" : "Souporaba", + "Security" : "Varnost", + "Groupware" : "Skupinsko delo", + "Personal info" : "Osebni podatki", + "Mobile & desktop" : "Mobilni in namizni dostop" +},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sq.js b/docker/overlays/nextcloud/html/lib/l10n/sq.js new file mode 100644 index 0000000..dd22c8c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sq.js @@ -0,0 +1,186 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Nuk shkruhet dot te drejtoria \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Zakonisht kjo mund të ndreqet duke i akorduar shërbyesit web të drejta shkrimi mbi drejtorinë e formësimeve", + "See %s" : "Shihni %s", + "Sample configuration detected" : "U gjet formësim shembull", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "U pa se është kopjuar shembulli për formësime. Kjo mund të prishë instalimin tuaj dhe nuk mbulohet. Ju lutemi, lexoni dokumentimin, përpara se të kryeni ndryshime te config.php", + "%1$s and %2$s" : "%1$s dhe %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s dhe %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s dhe %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s dhe %5$s", + "Education Edition" : "Variant Edukativ", + "Enterprise bundle" : "Pakoja e ndërmarrjeve", + "Groupware bundle" : "Pako groupware", + "Social sharing bundle" : "Pakoja e ndarjes sociale", + "PHP %s or higher is required." : "Kërkohet PHP %s ose më sipër.", + "PHP with a version lower than %s is required." : "Lypset PHP me një version më të ulët se sa %s.", + "%sbit or higher PHP required." : "Lypset PHP %sbit ose më i ri.", + "The command line tool %s could not be found" : "Mjeti rresht urdhrash %s s’u gjet dot", + "The library %s is not available." : "Libraria %s s’është e passhme.", + "Server version %s or higher is required." : "Versioni i serverit kërkohet %s ose më lartë", + "Server version %s or lower is required." : "Versioni i serverit kërkohet %s ose më poshtë", + "Authentication" : "Mirëfilltësim", + "Unknown filetype" : "Lloj i panjohur skedari", + "Invalid image" : "Figurë e pavlefshme", + "Avatar image is not square" : "Imazhi avatar nuk është katror", + "today" : "sot", + "yesterday" : "dje", + "_%n day ago_::_%n days ago_" : ["%n ditë më parë","%n ditë më parë"], + "last month" : "muajin e shkuar", + "_%n month ago_::_%n months ago_" : ["%n muaj më parë","%n muaj më parë"], + "last year" : "vitin e shkuar", + "_%n year ago_::_%n years ago_" : ["%n vit më parë","%n vjet më parë"], + "_%n hour ago_::_%n hours ago_" : ["%n orë më parë","%n orë më parë"], + "_%n minute ago_::_%n minutes ago_" : ["%n minutë më parë","%n minuta më parë"], + "seconds ago" : "sekonda më parë", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Moduli me ID: %s nuk ekziston. Ju lutem aktivizojeni atë në konfigurimet e aplikacionit tuaj ose kontaktoni administratorin tuaj.", + "File name is a reserved word" : "Emri i kartelës është një emër i rezervuar", + "File name contains at least one invalid character" : "Emri i kartelës përmban të paktën një shenjë të pavlefshme", + "File name is too long" : "Emri i kartelës është shumë i gjatë", + "Dot files are not allowed" : "Nuk lejohen kartela të fshehura", + "Empty filename is not allowed" : "Nuk lejohen emra të zbrazët kartelash", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikacioni \"%s\" s’mund të instalohet, ngaqë s’lexohet dot kartela appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikacioni \"%s\" nuk mund të instalohet sepse nuk përputhet me këtë version të serverit.", + "__language_name__" : "Shqip", + "This is an automatically sent email, please do not reply." : "Ky është një email i dërguar automatikisht, ju lutem mos u përgjigjni.", + "Help" : "Ndihmë", + "Apps" : "Aplikacione", + "Settings" : "Konfigurime", + "Log out" : "Shkyçu", + "Users" : "Përdorues", + "Unknown user" : "Përdorues i panjohur", + "Additional settings" : "Konfigurime shtesë", + "%s enter the database username and name." : "%s jepni emrin e bazës së të dhënave dhe emrin e përdoruesit për të.", + "%s enter the database username." : "%s jepni emrin e përdoruesit të bazës së të dhënave.", + "%s enter the database name." : "%s jepni emrin e bazës së të dhënave.", + "%s you may not use dots in the database name" : "%s s’mund të përdorni pika te emri i bazës së të dhënave", + "You need to enter details of an existing account." : "Duhet të futni detajet e një llogarie ekzistuese.", + "Oracle connection could not be established" : "S’u vendos dot lidhje me Oracle", + "Oracle username and/or password not valid" : "Emër përdoruesi dhe/ose fjalëkalim Oracle-i i pavlefshëm", + "PostgreSQL username and/or password not valid" : "Emër përdoruesi dhe/ose fjalëkalim PostgreSQL jo të vlefshëm", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nuk mbulohet dhe %s s’do të funksionojë si duhet në këtë platformë. Përdoreni nën përgjegjësinë tuaj! ", + "For the best results, please consider using a GNU/Linux server instead." : "Për përfundimet më të mira, ju lutemi, më mirë konsideroni përdorimin e një shërbyesi GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Duket se kjo instancë %s xhiron një mjedis PHP 32-bitësh dhe open_basedir është e formësuar, te php.ini. Kjo do të shpjerë në probleme me kartela më të mëdha se 4 GB dhe këshillohet me forcë të mos ndodhë.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Ju lutemi, hiqeni rregullimin open_basedir nga php.ini juaj ose hidhuni te PHP për 64-bit.", + "Set an admin username." : "Caktoni një emër përdoruesi për përgjegjësin.", + "Set an admin password." : "Caktoni një fjalëkalim për përgjegjësin.", + "Can't create or write into the data directory %s" : "S’e krijon ose s’shkruan dot te drejtoria e të dhënave %s", + "Invalid Federated Cloud ID" : "ID Federated Cloud e pavlefshme", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Mekanizmi i shërbimit për ndarje %s duhet të sendërtojë ndërfaqen OCP\\Share_Backend", + "Sharing backend %s not found" : "S’u gjet mekanizmi i shërbimit për ndarje %s", + "Sharing backend for %s not found" : "S’u gjet mekanizmi i shërbimit për ndarje për %s", + "Open »%s«" : "Hap»1 %s«", + "You are not allowed to share %s" : "Nuk ju lejohet ta ndani %s me të tjerët", + "Can’t increase permissions of %s" : "Nuk mund të shtohen lejet e %s", + "Files can’t be shared with delete permissions" : "Skedarët nuk mund të ndahen me leje të fshira", + "Files can’t be shared with create permissions" : "matchSkedarët nuk mund të ndahen me leje të krijuara", + "Expiration date is in the past" : "Data e skadimit bie në të kaluarën", + "Can’t set expiration date more than %s days in the future" : "Nuk mund të caktohet data e skadimit më shumë se %s ditë në të ardhmen", + "Click the button below to open it." : "Kliko butonin më poshtë për të hapur atë.", + "The requested share does not exist anymore" : "Ndarja e kërkuar nuk ekziston më", + "Could not find category \"%s\"" : "S’u gjet kategori \"%s\"", + "Sunday" : "E Dielë", + "Monday" : "E Hënë", + "Tuesday" : "E Martë", + "Wednesday" : "E Mërkurë", + "Thursday" : "E Enjte", + "Friday" : "E Premte", + "Saturday" : "E Shtunë", + "Sun." : "Die.", + "Mon." : "Hën.", + "Tue." : "Mar.", + "Wed." : "Mër.", + "Thu." : "Enj.", + "Fri." : "Pre.", + "Sat." : "Sht.", + "Su" : "Di", + "Mo" : "Hë", + "Tu" : "Ma", + "We" : "Ne", + "Th" : "En", + "Fr" : "Pr", + "Sa" : "Sh", + "January" : "Janar", + "February" : "Shkurt", + "March" : "Mars", + "April" : "Prill", + "May" : "Maj", + "June" : "Qershor", + "July" : "Korrik", + "August" : "Gusht", + "September" : "Shtator", + "October" : "Tetor", + "November" : "Nëntor", + "December" : "Dhjetor", + "Jan." : "Jan.", + "Feb." : "Shk.", + "Mar." : "Mar.", + "Apr." : "Pri.", + "May." : "Maj.", + "Jun." : "Qer.", + "Jul." : "Kor.", + "Aug." : "Gus.", + "Sep." : "Sht.", + "Oct." : "Tet.", + "Nov." : "Nën.", + "Dec." : "Dhj.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Në një emër përdoruesi lejohen vetëm shenjat vijuese: \"a-z\", \"A-Z\", \"0-9\", dhe \"_.@-\"", + "A valid username must be provided" : "Duhet dhënë një emër i vlefshëm përdoruesi", + "Username contains whitespace at the beginning or at the end" : "Emri i përdoruesit përmban hapësirë në fillim ose në fund", + "Username must not consist of dots only" : "Emri i përdoruesit nuk duhet të përbëhet vetëm nga pika", + "A valid password must be provided" : "Duhet dhënë një fjalëkalim i vlefshëm", + "The username is already being used" : "Emri i përdoruesit është tashmë i përdorur", + "User disabled" : "Përdorues i çaktivizuar", + "Login canceled by app" : "Hyrja u anulua nga aplikacioni", + "a safe home for all your data" : "Një shtëpi e sigurt për të dhënat e tua", + "File is currently busy, please try again later" : "Kartela tani është e zënë, ju lutemi, riprovoni më vonë.", + "Can't read file" : "S'lexohet dot kartela", + "Application is not enabled" : "Aplikacioni s’është aktivizuar", + "Authentication error" : "Gabim mirëfilltësimi", + "Token expired. Please reload page." : "Token-i ka skaduar. Ju lutem ringarkoni faqen.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "S’ka baza të dhënash (sqlite, mysql, ose postgresql) të instaluara.", + "Cannot write into \"config\" directory" : "S’shkruhet dot te drejtoria \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Kjo zakonisht mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria config. Shih %s", + "Cannot write into \"apps\" directory" : "S’shkruhet dot te drejtoria \"apps\"", + "Cannot create \"data\" directory" : "Nuk mund të krijohet direktoria \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Kjo zakonisht mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria rrënjë. Shih %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Zakonisht lejet mund të rregullohen duke i dhënë serverit të web-it akses shkrimi tek direktoria rrënjë. Shih %s.", + "Setting locale to %s failed" : "Caktimi i gjuhës si %s dështoi", + "Please install one of these locales on your system and restart your webserver." : "Ju lutemi, instaloni te sistemi juaj një prej këtyre vendoreve dhe rinisni shërbyesin tuaj web.", + "PHP module %s not installed." : "Moduli PHP %s s’është i instaluar.", + "Please ask your server administrator to install the module." : "Ju lutemi, kërkojini përgjegjësit të shërbyesit ta instalojë modulin.", + "PHP setting \"%s\" is not set to \"%s\"." : "Rregullimi PHP \"%s\" s’është vënë si \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Përshtatja e këtij konfigurimi në php.ini do e bëjë Nextcloud të punoj përsëri", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload është caktuar si \"%s\", në vend të vlerës së pritshme \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Për ta ndrequr këtë problem, caktoni për mbstring.func_overload vlerën 0 te php.ini juaj", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Lypset të paktën libxml2 2.7.0. Hëpërhë e instaluar është %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Për të ndrequr këtë problem, përditësoni libxml2 dhe rinisni shërbyesin tuaj web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Me sa duket, PHP-ja është rregulluar që të heqë blloqe të brendshëm dokumentimi. Kjo do t’i nxjerrë nga funksionimi disa aplikacione bazë.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Kjo ka gjasa të jetë shkaktuar nga një fshehtinë/përshpejtues i tillë si Zend OPcache ose eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Modulet PHP janë instaluar, por tregohen ende sikur mungojnë?", + "Please ask your server administrator to restart the web server." : "Ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj të rinisë shërbyesin web.", + "PostgreSQL >= 9 required" : "Lypset PostgreSQL >= 9", + "Please upgrade your database version" : "Ju lutemi, përmirësoni bazën tuaj të të dhënave me një version më të ri.", + "Your data directory is readable by other users" : "Direktoria juaj e të dhënave është e lexueshme nga përdorues të tjerë", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Ju lutemi, kalojani lejet në 0770, që kështu atë drejtori të mos mund ta shfaqin përdorues të tjerë.", + "Your data directory must be an absolute path" : "Direktoria juaj e të dhënave duhet të jetë një path absolut", + "Check the value of \"datadirectory\" in your configuration" : "Kontrolloni vlerën e \"datadirectory\" te formësimi juaj", + "Your data directory is invalid" : "Direktoria juaj e të dhënave është i pavlefshëm", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Sigurohu që ekziston një skedar i quajtur \".ocdata\" në rrënjën e direktorisë së të dhënave.", + "Could not obtain lock type %d on \"%s\"." : "S’u mor dot lloj kyçjeje %d në \"%s\".", + "Storage unauthorized. %s" : "Depozitë e paautorizuar. %s", + "Storage incomplete configuration. %s" : "Formësim jo i plotë i depozitës. %s", + "Storage connection error. %s" : "Gabim lidhje te depozita. %s", + "Storage is temporarily not available" : "Hapsira ruajtëse nuk është në dispozicion përkohësisht", + "Storage connection timeout. %s" : "Mbarim kohe lidhjeje për depozitën. %s", + "Following databases are supported: %s" : "Mbulohen bazat vijuese të të dhënave: %s", + "Following platforms are supported: %s" : "Mbulohen platformat vijuese: %s", + "Basic settings" : "Konfigurime bazike", + "Sharing" : "Ndarja", + "Security" : "Siguria", + "Personal info" : "Informacion personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Zakonisht kjo mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria e aplikacioneve ose duke çaktivizuar appstore në skedarin config. Shih %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sq.json b/docker/overlays/nextcloud/html/lib/l10n/sq.json new file mode 100644 index 0000000..430e2fd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sq.json @@ -0,0 +1,184 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Nuk shkruhet dot te drejtoria \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Zakonisht kjo mund të ndreqet duke i akorduar shërbyesit web të drejta shkrimi mbi drejtorinë e formësimeve", + "See %s" : "Shihni %s", + "Sample configuration detected" : "U gjet formësim shembull", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "U pa se është kopjuar shembulli për formësime. Kjo mund të prishë instalimin tuaj dhe nuk mbulohet. Ju lutemi, lexoni dokumentimin, përpara se të kryeni ndryshime te config.php", + "%1$s and %2$s" : "%1$s dhe %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s dhe %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s dhe %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s dhe %5$s", + "Education Edition" : "Variant Edukativ", + "Enterprise bundle" : "Pakoja e ndërmarrjeve", + "Groupware bundle" : "Pako groupware", + "Social sharing bundle" : "Pakoja e ndarjes sociale", + "PHP %s or higher is required." : "Kërkohet PHP %s ose më sipër.", + "PHP with a version lower than %s is required." : "Lypset PHP me një version më të ulët se sa %s.", + "%sbit or higher PHP required." : "Lypset PHP %sbit ose më i ri.", + "The command line tool %s could not be found" : "Mjeti rresht urdhrash %s s’u gjet dot", + "The library %s is not available." : "Libraria %s s’është e passhme.", + "Server version %s or higher is required." : "Versioni i serverit kërkohet %s ose më lartë", + "Server version %s or lower is required." : "Versioni i serverit kërkohet %s ose më poshtë", + "Authentication" : "Mirëfilltësim", + "Unknown filetype" : "Lloj i panjohur skedari", + "Invalid image" : "Figurë e pavlefshme", + "Avatar image is not square" : "Imazhi avatar nuk është katror", + "today" : "sot", + "yesterday" : "dje", + "_%n day ago_::_%n days ago_" : ["%n ditë më parë","%n ditë më parë"], + "last month" : "muajin e shkuar", + "_%n month ago_::_%n months ago_" : ["%n muaj më parë","%n muaj më parë"], + "last year" : "vitin e shkuar", + "_%n year ago_::_%n years ago_" : ["%n vit më parë","%n vjet më parë"], + "_%n hour ago_::_%n hours ago_" : ["%n orë më parë","%n orë më parë"], + "_%n minute ago_::_%n minutes ago_" : ["%n minutë më parë","%n minuta më parë"], + "seconds ago" : "sekonda më parë", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Moduli me ID: %s nuk ekziston. Ju lutem aktivizojeni atë në konfigurimet e aplikacionit tuaj ose kontaktoni administratorin tuaj.", + "File name is a reserved word" : "Emri i kartelës është një emër i rezervuar", + "File name contains at least one invalid character" : "Emri i kartelës përmban të paktën një shenjë të pavlefshme", + "File name is too long" : "Emri i kartelës është shumë i gjatë", + "Dot files are not allowed" : "Nuk lejohen kartela të fshehura", + "Empty filename is not allowed" : "Nuk lejohen emra të zbrazët kartelash", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplikacioni \"%s\" s’mund të instalohet, ngaqë s’lexohet dot kartela appinfo.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplikacioni \"%s\" nuk mund të instalohet sepse nuk përputhet me këtë version të serverit.", + "__language_name__" : "Shqip", + "This is an automatically sent email, please do not reply." : "Ky është një email i dërguar automatikisht, ju lutem mos u përgjigjni.", + "Help" : "Ndihmë", + "Apps" : "Aplikacione", + "Settings" : "Konfigurime", + "Log out" : "Shkyçu", + "Users" : "Përdorues", + "Unknown user" : "Përdorues i panjohur", + "Additional settings" : "Konfigurime shtesë", + "%s enter the database username and name." : "%s jepni emrin e bazës së të dhënave dhe emrin e përdoruesit për të.", + "%s enter the database username." : "%s jepni emrin e përdoruesit të bazës së të dhënave.", + "%s enter the database name." : "%s jepni emrin e bazës së të dhënave.", + "%s you may not use dots in the database name" : "%s s’mund të përdorni pika te emri i bazës së të dhënave", + "You need to enter details of an existing account." : "Duhet të futni detajet e një llogarie ekzistuese.", + "Oracle connection could not be established" : "S’u vendos dot lidhje me Oracle", + "Oracle username and/or password not valid" : "Emër përdoruesi dhe/ose fjalëkalim Oracle-i i pavlefshëm", + "PostgreSQL username and/or password not valid" : "Emër përdoruesi dhe/ose fjalëkalim PostgreSQL jo të vlefshëm", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nuk mbulohet dhe %s s’do të funksionojë si duhet në këtë platformë. Përdoreni nën përgjegjësinë tuaj! ", + "For the best results, please consider using a GNU/Linux server instead." : "Për përfundimet më të mira, ju lutemi, më mirë konsideroni përdorimin e një shërbyesi GNU/Linux.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Duket se kjo instancë %s xhiron një mjedis PHP 32-bitësh dhe open_basedir është e formësuar, te php.ini. Kjo do të shpjerë në probleme me kartela më të mëdha se 4 GB dhe këshillohet me forcë të mos ndodhë.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Ju lutemi, hiqeni rregullimin open_basedir nga php.ini juaj ose hidhuni te PHP për 64-bit.", + "Set an admin username." : "Caktoni një emër përdoruesi për përgjegjësin.", + "Set an admin password." : "Caktoni një fjalëkalim për përgjegjësin.", + "Can't create or write into the data directory %s" : "S’e krijon ose s’shkruan dot te drejtoria e të dhënave %s", + "Invalid Federated Cloud ID" : "ID Federated Cloud e pavlefshme", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Mekanizmi i shërbimit për ndarje %s duhet të sendërtojë ndërfaqen OCP\\Share_Backend", + "Sharing backend %s not found" : "S’u gjet mekanizmi i shërbimit për ndarje %s", + "Sharing backend for %s not found" : "S’u gjet mekanizmi i shërbimit për ndarje për %s", + "Open »%s«" : "Hap»1 %s«", + "You are not allowed to share %s" : "Nuk ju lejohet ta ndani %s me të tjerët", + "Can’t increase permissions of %s" : "Nuk mund të shtohen lejet e %s", + "Files can’t be shared with delete permissions" : "Skedarët nuk mund të ndahen me leje të fshira", + "Files can’t be shared with create permissions" : "matchSkedarët nuk mund të ndahen me leje të krijuara", + "Expiration date is in the past" : "Data e skadimit bie në të kaluarën", + "Can’t set expiration date more than %s days in the future" : "Nuk mund të caktohet data e skadimit më shumë se %s ditë në të ardhmen", + "Click the button below to open it." : "Kliko butonin më poshtë për të hapur atë.", + "The requested share does not exist anymore" : "Ndarja e kërkuar nuk ekziston më", + "Could not find category \"%s\"" : "S’u gjet kategori \"%s\"", + "Sunday" : "E Dielë", + "Monday" : "E Hënë", + "Tuesday" : "E Martë", + "Wednesday" : "E Mërkurë", + "Thursday" : "E Enjte", + "Friday" : "E Premte", + "Saturday" : "E Shtunë", + "Sun." : "Die.", + "Mon." : "Hën.", + "Tue." : "Mar.", + "Wed." : "Mër.", + "Thu." : "Enj.", + "Fri." : "Pre.", + "Sat." : "Sht.", + "Su" : "Di", + "Mo" : "Hë", + "Tu" : "Ma", + "We" : "Ne", + "Th" : "En", + "Fr" : "Pr", + "Sa" : "Sh", + "January" : "Janar", + "February" : "Shkurt", + "March" : "Mars", + "April" : "Prill", + "May" : "Maj", + "June" : "Qershor", + "July" : "Korrik", + "August" : "Gusht", + "September" : "Shtator", + "October" : "Tetor", + "November" : "Nëntor", + "December" : "Dhjetor", + "Jan." : "Jan.", + "Feb." : "Shk.", + "Mar." : "Mar.", + "Apr." : "Pri.", + "May." : "Maj.", + "Jun." : "Qer.", + "Jul." : "Kor.", + "Aug." : "Gus.", + "Sep." : "Sht.", + "Oct." : "Tet.", + "Nov." : "Nën.", + "Dec." : "Dhj.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Në një emër përdoruesi lejohen vetëm shenjat vijuese: \"a-z\", \"A-Z\", \"0-9\", dhe \"_.@-\"", + "A valid username must be provided" : "Duhet dhënë një emër i vlefshëm përdoruesi", + "Username contains whitespace at the beginning or at the end" : "Emri i përdoruesit përmban hapësirë në fillim ose në fund", + "Username must not consist of dots only" : "Emri i përdoruesit nuk duhet të përbëhet vetëm nga pika", + "A valid password must be provided" : "Duhet dhënë një fjalëkalim i vlefshëm", + "The username is already being used" : "Emri i përdoruesit është tashmë i përdorur", + "User disabled" : "Përdorues i çaktivizuar", + "Login canceled by app" : "Hyrja u anulua nga aplikacioni", + "a safe home for all your data" : "Një shtëpi e sigurt për të dhënat e tua", + "File is currently busy, please try again later" : "Kartela tani është e zënë, ju lutemi, riprovoni më vonë.", + "Can't read file" : "S'lexohet dot kartela", + "Application is not enabled" : "Aplikacioni s’është aktivizuar", + "Authentication error" : "Gabim mirëfilltësimi", + "Token expired. Please reload page." : "Token-i ka skaduar. Ju lutem ringarkoni faqen.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "S’ka baza të dhënash (sqlite, mysql, ose postgresql) të instaluara.", + "Cannot write into \"config\" directory" : "S’shkruhet dot te drejtoria \"config\"", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Kjo zakonisht mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria config. Shih %s", + "Cannot write into \"apps\" directory" : "S’shkruhet dot te drejtoria \"apps\"", + "Cannot create \"data\" directory" : "Nuk mund të krijohet direktoria \"data\"", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Kjo zakonisht mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria rrënjë. Shih %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Zakonisht lejet mund të rregullohen duke i dhënë serverit të web-it akses shkrimi tek direktoria rrënjë. Shih %s.", + "Setting locale to %s failed" : "Caktimi i gjuhës si %s dështoi", + "Please install one of these locales on your system and restart your webserver." : "Ju lutemi, instaloni te sistemi juaj një prej këtyre vendoreve dhe rinisni shërbyesin tuaj web.", + "PHP module %s not installed." : "Moduli PHP %s s’është i instaluar.", + "Please ask your server administrator to install the module." : "Ju lutemi, kërkojini përgjegjësit të shërbyesit ta instalojë modulin.", + "PHP setting \"%s\" is not set to \"%s\"." : "Rregullimi PHP \"%s\" s’është vënë si \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Përshtatja e këtij konfigurimi në php.ini do e bëjë Nextcloud të punoj përsëri", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload është caktuar si \"%s\", në vend të vlerës së pritshme \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Për ta ndrequr këtë problem, caktoni për mbstring.func_overload vlerën 0 te php.ini juaj", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Lypset të paktën libxml2 2.7.0. Hëpërhë e instaluar është %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Për të ndrequr këtë problem, përditësoni libxml2 dhe rinisni shërbyesin tuaj web.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Me sa duket, PHP-ja është rregulluar që të heqë blloqe të brendshëm dokumentimi. Kjo do t’i nxjerrë nga funksionimi disa aplikacione bazë.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Kjo ka gjasa të jetë shkaktuar nga një fshehtinë/përshpejtues i tillë si Zend OPcache ose eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Modulet PHP janë instaluar, por tregohen ende sikur mungojnë?", + "Please ask your server administrator to restart the web server." : "Ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj të rinisë shërbyesin web.", + "PostgreSQL >= 9 required" : "Lypset PostgreSQL >= 9", + "Please upgrade your database version" : "Ju lutemi, përmirësoni bazën tuaj të të dhënave me një version më të ri.", + "Your data directory is readable by other users" : "Direktoria juaj e të dhënave është e lexueshme nga përdorues të tjerë", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Ju lutemi, kalojani lejet në 0770, që kështu atë drejtori të mos mund ta shfaqin përdorues të tjerë.", + "Your data directory must be an absolute path" : "Direktoria juaj e të dhënave duhet të jetë një path absolut", + "Check the value of \"datadirectory\" in your configuration" : "Kontrolloni vlerën e \"datadirectory\" te formësimi juaj", + "Your data directory is invalid" : "Direktoria juaj e të dhënave është i pavlefshëm", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Sigurohu që ekziston një skedar i quajtur \".ocdata\" në rrënjën e direktorisë së të dhënave.", + "Could not obtain lock type %d on \"%s\"." : "S’u mor dot lloj kyçjeje %d në \"%s\".", + "Storage unauthorized. %s" : "Depozitë e paautorizuar. %s", + "Storage incomplete configuration. %s" : "Formësim jo i plotë i depozitës. %s", + "Storage connection error. %s" : "Gabim lidhje te depozita. %s", + "Storage is temporarily not available" : "Hapsira ruajtëse nuk është në dispozicion përkohësisht", + "Storage connection timeout. %s" : "Mbarim kohe lidhjeje për depozitën. %s", + "Following databases are supported: %s" : "Mbulohen bazat vijuese të të dhënave: %s", + "Following platforms are supported: %s" : "Mbulohen platformat vijuese: %s", + "Basic settings" : "Konfigurime bazike", + "Sharing" : "Ndarja", + "Security" : "Siguria", + "Personal info" : "Informacion personal", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Zakonisht kjo mund të rregullohet duke i dhënë serverit të web-it akses shkrimi tek direktoria e aplikacioneve ose duke çaktivizuar appstore në skedarin config. Shih %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sr.js b/docker/overlays/nextcloud/html/lib/l10n/sr.js new file mode 100644 index 0000000..252d557 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sr.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Не могу да уписујем у „config“ директоријум!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ово се обично може средити давањем права веб серверу да пише у директоријум са подешавањима", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Или, ако пре желите да задржите config.php да је само за читање, поставите опцију \"config_is_read_only\" на true у њему самом.", + "See %s" : "Погледајте %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ово се обично може средити давањем права веб серверу да пише у директоријум са подешавањима.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Или, ако пре желите да задржите config.php да је само за читање, поставите опцију \"config_is_read_only\" на true у њему самом. Погледајте %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Фајлови апликације „%1$s“ нису правилно замењени. Проверите да ли је верзија компатибилна са сервером.", + "Sample configuration detected" : "Откривен је пример подешавања", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Откривено је да је прекопиран пример подешавања. Ово може покварити инсталацију и није подржано. Прочитајте документацију пре вршења промена у фајлу config.php", + "Other activities" : "Остале активности", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Образовно издање", + "Enterprise bundle" : "Комплет за предузећа", + "Groupware bundle" : "Комплет за радне тимове", + "Hub bundle" : "Чвориште пакета", + "Social sharing bundle" : "Комплет за друштвене мреже", + "PHP %s or higher is required." : "Потребан је PHP %s или новији.", + "PHP with a version lower than %s is required." : "Потребна је PHP верзија старија од верзије %s.", + "%sbit or higher PHP required." : "Потребна је верзија PHP-а једнака или већа од верзије %s.", + "The following architectures are supported: %s" : "Подржане су следеће архитектуре: %s", + "The following databases are supported: %s" : "Подржане су следеће базе података: %s", + "The command line tool %s could not be found" : "Алатку командне линије „%s“ није могуће пронаћи", + "The library %s is not available." : "Библиотека „%s“ није доступна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Потребна је библиотека „%1$s“ верзије веће од %2$s - доступна верзија је %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Потребна је библиотека „%1$s“ верзије ниже од %2$s - доступна верзија је %3$s.", + "The following platforms are supported: %s" : "Подржане су следеће платформе: %s", + "Server version %s or higher is required." : "Потребна је верзија сервера %s или виша.", + "Server version %s or lower is required." : "Потребна је верзија сервера %s или нижа.", + "Logged in user must be an admin or sub admin" : "Пријављени корисник мора бити администратор или подадминистратор", + "Logged in user must be an admin" : "Пријављени корисник мора бити администратор", + "Wiping of device %s has started" : "Започето брисање уређаја %s", + "Wiping of device »%s« has started" : "Удаљено брисање уређаја „%s“ је почело", + "»%s« started remote wipe" : "„%s“ је започео удаљено брисање", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Уређај или апликација „%s“ је започео процес удаљеног брисања. Добићете још један и-мејл када се процес заврши.", + "Wiping of device %s has finished" : "Брисање уређаја %s завршено", + "Wiping of device »%s« has finished" : "Брисање уређаја „%s“ завршено", + "»%s« finished remote wipe" : "„%s“ је завршио удаљено брисање", + "Device or application »%s« has finished the remote wipe process." : "Уређај или апликација „%s“ је завршио процес удаљеног брисања.", + "Remote wipe started" : "Започето удаљено брисање", + "A remote wipe was started on device %s" : "Започето удаљено брисање на уређају %s", + "Remote wipe finished" : "Удаљено брисање завршено", + "The remote wipe on %s has finished" : "Удаљено брисање на уређају %s завршено", + "Authentication" : "Провера идентитета", + "Unknown filetype" : "Непознат тип фајла", + "Invalid image" : "Неисправна слика", + "Avatar image is not square" : "Слика аватара није квадратна", + "today" : "данас", + "tomorrow" : "сутра", + "yesterday" : "јуче", + "_in %n day_::_in %n days_" : ["за %n дан","за %n дана","за %n дана"], + "_%n day ago_::_%n days ago_" : ["пре %n дан","пре %n дана","пре %n дана"], + "next month" : "следећег месеца", + "last month" : "прошлог месеца", + "_in %n month_::_in %n months_" : ["за %n месец","за %n месеца","за %n месеци"], + "_%n month ago_::_%n months ago_" : ["пре %n месец","пре %n месеца","пре %n месеци"], + "next year" : "следеће године", + "last year" : "прошле године", + "_in %n year_::_in %n years_" : ["за %n годину","за %n године","за %n година"], + "_%n year ago_::_%n years ago_" : ["пре %n годину","пре %n године","пре %n година"], + "_in %n hour_::_in %n hours_" : ["за %n сат","за %n сата","за %n сати"], + "_%n hour ago_::_%n hours ago_" : ["пре %n сат","пре %n сата","пре %n сати"], + "_in %n minute_::_in %n minutes_" : ["за %n минут","за %n минута","за %n минута"], + "_%n minute ago_::_%n minutes ago_" : ["пре %n минут","пре %n минута","пре %n минута"], + "in a few seconds" : "за пар секунди", + "seconds ago" : "пре неколико секунди", + "Empty file" : "Празан фајл", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модул са идентификацијом: %s не постоји. Омогућите га у подешавањима апликација или контактирајте администратора.", + "File name is a reserved word" : "Назив фајла је резервисана реч", + "File name contains at least one invalid character" : "Назив фајла садржи бар један недозвољен знак", + "File name is too long" : "Назив фајла је предугачак", + "Dot files are not allowed" : "Фајлови са почетном тачком нису дозвољени", + "Empty filename is not allowed" : "Празан назив није дозвољен", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Апликација \"%s\" не може бити инсталирана јер appinfo фајл не може да се прочита.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Апликација \"%s\" не може бити инсталирана јер није компатибилна са овом верзијом сервера.", + "__language_name__" : "Српски", + "This is an automatically sent email, please do not reply." : "Ово је аутоматски генерисана порука, не одговарајте на њу.", + "Help" : "Помоћ", + "Apps" : "Апликације", + "Settings" : "Поставке", + "Log out" : "Одјава", + "Users" : "Корисници", + "Unknown user" : "Непознат корисник", + "Additional settings" : "Додатне поставке", + "%s enter the database username and name." : "%s унеси корисничко име базе података и име.", + "%s enter the database username." : "%s унеси корисничко име базе података.", + "%s enter the database name." : "%s унеси име базе података.", + "%s you may not use dots in the database name" : "%s не можете користити тачке у имену базе података", + "MySQL username and/or password not valid" : "MySQL корисничко име и/или лозинка нису исправни", + "You need to enter details of an existing account." : "Потребно је да унесете детаље постојећег налога.", + "Oracle connection could not be established" : "Веза са базом података Oracle не може бити успостављена", + "Oracle username and/or password not valid" : "Oracle корисничко име и/или лозинка нису исправни", + "PostgreSQL username and/or password not valid" : "PostgreSQL корисничко име и/или лозинка нису исправни", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Мек ОС Икс није подржан и %s неће радити исправно на овој платформи. Користите га на сопствени ризик!", + "For the best results, please consider using a GNU/Linux server instead." : "За најбоље резултате, размотрите употребу ГНУ/Линукс сервера.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Изгледа да %s ради у 32-битном PHP окружењу а open_basedir је подешен у php.ini фајлу. То може довести до проблема са фајловима већим од 4 GB, те стога није препоручљиво.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Уклоните open_basedir поставку из php.ini фајла или пређите на 64-битни PHP.", + "Set an admin username." : "Поставите име за администратора.", + "Set an admin password." : "Поставите лозинку за администратора.", + "Can't create or write into the data directory %s" : "Не могу креирати или уписивати у директоријум података %s", + "Invalid Federated Cloud ID" : "Неисправан ИД Здруженог облака", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Позадина дељења %s мора користити корисничко окружење OCP\\Share_Backend", + "Sharing backend %s not found" : "Позадина за дељење %s није пронађена", + "Sharing backend for %s not found" : "Позадина за дељење за %s није пронађена", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s је поделио „%2$s“ са Вама и жели да дода:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s је поделио „%2$s“ са Вама и жели да дода", + "»%s« added a note to a file shared with you" : "\"%s\" је додао белешку на фајл који дели са Вама", + "Open »%s«" : "Отвори „%s“", + "%1$s via %2$s" : "%1$s преко %2$s", + "You are not allowed to share %s" : "Није вам дозвољено да делите %s", + "Can’t increase permissions of %s" : "Не могу да повећам дозволе за %s", + "Files can’t be shared with delete permissions" : "Фајлови не могу бити дељени са дозволама за брисање", + "Files can’t be shared with create permissions" : "Фајлови не могу бити дељени са дозволама за креирање", + "Expiration date is in the past" : "Датум истека је у прошлости", + "Can’t set expiration date more than %s days in the future" : "Не могу да поставим датум истека више од %s дана у будућност", + "%1$s shared »%2$s« with you" : "%1$s је поделио „%2$s“ са Вама", + "%1$s shared »%2$s« with you." : "%1$s је поделио „%2$s“ са Вама.", + "Click the button below to open it." : "Кликните дугме испод да га отворите.", + "The requested share does not exist anymore" : "Захтевано дељење више не постоји", + "Could not find category \"%s\"" : "Не могу да пронађем категорију „%s“.", + "Sunday" : "Недеља", + "Monday" : "Понедељак", + "Tuesday" : "Уторак", + "Wednesday" : "Среда", + "Thursday" : "Четвртак", + "Friday" : "Петак", + "Saturday" : "Субота", + "Sun." : "Нед", + "Mon." : "Пон", + "Tue." : "Уто", + "Wed." : "Сре", + "Thu." : "Чет", + "Fri." : "Пет", + "Sat." : "Суб", + "Su" : "Не", + "Mo" : "По", + "Tu" : "Ут", + "We" : "Ср", + "Th" : "Че", + "Fr" : "Пе", + "Sa" : "Су", + "January" : "Јануар", + "February" : "Фебруар", + "March" : "Март", + "April" : "Април", + "May" : "Мај", + "June" : "Јун", + "July" : "Јул", + "August" : "Август", + "September" : "Септембар", + "October" : "Октобар", + "November" : "Новембар", + "December" : "Децембар", + "Jan." : "Јан.", + "Feb." : "Феб.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Мај.", + "Jun." : "Јун.", + "Jul." : "Јул.", + "Aug." : "Авг.", + "Sep." : "Сеп.", + "Oct." : "Окт.", + "Nov." : "Нов.", + "Dec." : "Дец.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "У корисничком имену су дозвољени само следећи карактери: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Морате унети исправно корисничко име", + "Username contains whitespace at the beginning or at the end" : "Корисничко име садржи белине на почетку или на крају", + "Username must not consist of dots only" : "Корисничко име не могу бити само тачке", + "Username is invalid because files already exist for this user" : "Корисничко име није исправно пошто већ постоје фајлови за овог корисника", + "A valid password must be provided" : "Морате унети исправну лозинку", + "The username is already being used" : "Корисничко име се већ користи", + "Could not create user" : "Не могу да направим корисника", + "User disabled" : "Корисник онемогућен", + "Login canceled by app" : "Пријава отказана од стране апликације", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Апликација „%1$s“ не може бити инсталирана јер следеће зависности нису испуњене: %2$s", + "a safe home for all your data" : "сигурно место за све Ваше податке", + "File is currently busy, please try again later" : "Фајл је тренутно заузет, покушајте поново касније", + "Can't read file" : "Не могу да читам фајл", + "Application is not enabled" : "Апликација није укључена", + "Authentication error" : "Грешка при провери идентитета", + "Token expired. Please reload page." : "Жетон је истекао. Поново учитајте страницу.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Нема драјвера базе података (скулајт, мајскул или постгрескул).", + "Cannot write into \"config\" directory" : "Не могу уписивати у директоријуму „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ово се обично може средити давањем права писања веб серверу за директоријум са подешавањима. Погледајте %s", + "Cannot write into \"apps\" directory" : "Не могу уписивати у директоријуму „apps“", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ово се обично може поправити давањем права уписа веб серверу директоријум апликација или искуључивањем продавнице апликација у фајлу config file.", + "Cannot create \"data\" directory" : "Не могу да направим \"data\" директоријум", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ово се обично може поправити тако што веб серверу дате право уписа за корени директоријуму. Видети %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Привилегије се обично могу поправити тако што веб серверу дате право уписа за корени директоријуму. Видети %s", + "Setting locale to %s failed" : "Постављање локалитета на %s није успело", + "Please install one of these locales on your system and restart your webserver." : "Инсталирајте неки од ових локалитета на ваш систем и поново покрените веб сервер.", + "PHP module %s not installed." : "PHP модул %s није инсталиран.", + "Please ask your server administrator to install the module." : "Замолите администратора вашег сервера да инсталира тај модул.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP поставка „%s“ није постављена на „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Некстклауд ће прорадити поново када прилагодите ово подашавање у php.ini фајлу", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload је постављено на „%s“ уместо на очекивану вредност „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Да би решили овај проблем поставите mbstring.func_overload на 0 у фајлу php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Потребан је бар libxml2 2.7.0. Тренутно је инсталиран %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Да поправите овај проблем, ажурирајте верзију библиотеке libxml2 и рестартујте веб сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP је очигледно подешен да склања уметнуте doc блокове. То ће учинити неколико кључних апликација недоступним.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ово је вероватно изазвано кешом или акцелератором као што су ЗендОПкеш или еАкцелератор.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP модули су инсталирани али се и даље воде као недостајући?", + "Please ask your server administrator to restart the web server." : "Замолите вашег администратора сервера да поново покрене веб сервер.", + "PostgreSQL >= 9 required" : "Захтеван је ПостгреСкул >= 9", + "Please upgrade your database version" : "Надоградите ваше издање базе", + "Your data directory is readable by other users" : "Директоријум са подацима је читљив од стране других корисника система", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Промените дозволе у 0770 како директоријуми не би могли бити излистани од стране других корисника.", + "Your data directory must be an absolute path" : "Директоријум са подацима мора бити апсолутна путања", + "Check the value of \"datadirectory\" in your configuration" : "Проверите податак за \"datadirectory\" у вашој конфигурацији", + "Your data directory is invalid" : "Директоријум са подацима није исправан", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Уверите се да фајл \".ocdata\" постоји у корену директоријума са подацима.", + "Action \"%s\" not supported or implemented." : "Радња \"%s\" није подржана или имплементирана.", + "Authentication failed, wrong token or provider ID given" : "Неуспела провера идентитета, добијен погрешан токен или ID провајдера", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Недостају параметри да би се довршио захтев. Недостајући параметри: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Идентификацију \"%1$s\" већ користи провајдер здруживања \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Провајдер клауд здруживања са ID-ом \"%s\" не постоји", + "Could not obtain lock type %d on \"%s\"." : "Не могу да остварим закључаност %d за „%s“.", + "Storage unauthorized. %s" : "Складиште није овлашћено. %s", + "Storage incomplete configuration. %s" : "Непотпуна конфигурација складишта. %s", + "Storage connection error. %s" : "Грешка приликом повезивања на складиште. %s", + "Storage is temporarily not available" : "Складиште привремено није доступно", + "Storage connection timeout. %s" : "Истекло је време за повезивање на складиште. %s", + "Following databases are supported: %s" : "Подржане су следеће базе података: %s", + "Following platforms are supported: %s" : "Подржане су следеће платформе: %s", + "Overview" : "Преглед", + "Basic settings" : "Основне поставке", + "Sharing" : "Дељење", + "Security" : "Безбедност", + "Groupware" : "Радни тимови", + "Personal info" : "Лични подаци", + "Mobile & desktop" : "Мобилни и десктоп", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ово се обично може поправити тако што веб серверу дате приступ уписа за директоријум где су апликације или тако што онемогућите продавницу у config фајлу. Видети %s" +}, +"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sr.json b/docker/overlays/nextcloud/html/lib/l10n/sr.json new file mode 100644 index 0000000..ff93400 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sr.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Не могу да уписујем у „config“ директоријум!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Ово се обично може средити давањем права веб серверу да пише у директоријум са подешавањима", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Или, ако пре желите да задржите config.php да је само за читање, поставите опцију \"config_is_read_only\" на true у њему самом.", + "See %s" : "Погледајте %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Ово се обично може средити давањем права веб серверу да пише у директоријум са подешавањима.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Или, ако пре желите да задржите config.php да је само за читање, поставите опцију \"config_is_read_only\" на true у њему самом. Погледајте %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Фајлови апликације „%1$s“ нису правилно замењени. Проверите да ли је верзија компатибилна са сервером.", + "Sample configuration detected" : "Откривен је пример подешавања", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Откривено је да је прекопиран пример подешавања. Ово може покварити инсталацију и није подржано. Прочитајте документацију пре вршења промена у фајлу config.php", + "Other activities" : "Остале активности", + "%1$s and %2$s" : "%1$s и %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s и %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s и %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s и %5$s", + "Education Edition" : "Образовно издање", + "Enterprise bundle" : "Комплет за предузећа", + "Groupware bundle" : "Комплет за радне тимове", + "Hub bundle" : "Чвориште пакета", + "Social sharing bundle" : "Комплет за друштвене мреже", + "PHP %s or higher is required." : "Потребан је PHP %s или новији.", + "PHP with a version lower than %s is required." : "Потребна је PHP верзија старија од верзије %s.", + "%sbit or higher PHP required." : "Потребна је верзија PHP-а једнака или већа од верзије %s.", + "The following architectures are supported: %s" : "Подржане су следеће архитектуре: %s", + "The following databases are supported: %s" : "Подржане су следеће базе података: %s", + "The command line tool %s could not be found" : "Алатку командне линије „%s“ није могуће пронаћи", + "The library %s is not available." : "Библиотека „%s“ није доступна.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Потребна је библиотека „%1$s“ верзије веће од %2$s - доступна верзија је %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Потребна је библиотека „%1$s“ верзије ниже од %2$s - доступна верзија је %3$s.", + "The following platforms are supported: %s" : "Подржане су следеће платформе: %s", + "Server version %s or higher is required." : "Потребна је верзија сервера %s или виша.", + "Server version %s or lower is required." : "Потребна је верзија сервера %s или нижа.", + "Logged in user must be an admin or sub admin" : "Пријављени корисник мора бити администратор или подадминистратор", + "Logged in user must be an admin" : "Пријављени корисник мора бити администратор", + "Wiping of device %s has started" : "Започето брисање уређаја %s", + "Wiping of device »%s« has started" : "Удаљено брисање уређаја „%s“ је почело", + "»%s« started remote wipe" : "„%s“ је започео удаљено брисање", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Уређај или апликација „%s“ је започео процес удаљеног брисања. Добићете још један и-мејл када се процес заврши.", + "Wiping of device %s has finished" : "Брисање уређаја %s завршено", + "Wiping of device »%s« has finished" : "Брисање уређаја „%s“ завршено", + "»%s« finished remote wipe" : "„%s“ је завршио удаљено брисање", + "Device or application »%s« has finished the remote wipe process." : "Уређај или апликација „%s“ је завршио процес удаљеног брисања.", + "Remote wipe started" : "Започето удаљено брисање", + "A remote wipe was started on device %s" : "Започето удаљено брисање на уређају %s", + "Remote wipe finished" : "Удаљено брисање завршено", + "The remote wipe on %s has finished" : "Удаљено брисање на уређају %s завршено", + "Authentication" : "Провера идентитета", + "Unknown filetype" : "Непознат тип фајла", + "Invalid image" : "Неисправна слика", + "Avatar image is not square" : "Слика аватара није квадратна", + "today" : "данас", + "tomorrow" : "сутра", + "yesterday" : "јуче", + "_in %n day_::_in %n days_" : ["за %n дан","за %n дана","за %n дана"], + "_%n day ago_::_%n days ago_" : ["пре %n дан","пре %n дана","пре %n дана"], + "next month" : "следећег месеца", + "last month" : "прошлог месеца", + "_in %n month_::_in %n months_" : ["за %n месец","за %n месеца","за %n месеци"], + "_%n month ago_::_%n months ago_" : ["пре %n месец","пре %n месеца","пре %n месеци"], + "next year" : "следеће године", + "last year" : "прошле године", + "_in %n year_::_in %n years_" : ["за %n годину","за %n године","за %n година"], + "_%n year ago_::_%n years ago_" : ["пре %n годину","пре %n године","пре %n година"], + "_in %n hour_::_in %n hours_" : ["за %n сат","за %n сата","за %n сати"], + "_%n hour ago_::_%n hours ago_" : ["пре %n сат","пре %n сата","пре %n сати"], + "_in %n minute_::_in %n minutes_" : ["за %n минут","за %n минута","за %n минута"], + "_%n minute ago_::_%n minutes ago_" : ["пре %n минут","пре %n минута","пре %n минута"], + "in a few seconds" : "за пар секунди", + "seconds ago" : "пре неколико секунди", + "Empty file" : "Празан фајл", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модул са идентификацијом: %s не постоји. Омогућите га у подешавањима апликација или контактирајте администратора.", + "File name is a reserved word" : "Назив фајла је резервисана реч", + "File name contains at least one invalid character" : "Назив фајла садржи бар један недозвољен знак", + "File name is too long" : "Назив фајла је предугачак", + "Dot files are not allowed" : "Фајлови са почетном тачком нису дозвољени", + "Empty filename is not allowed" : "Празан назив није дозвољен", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Апликација \"%s\" не може бити инсталирана јер appinfo фајл не може да се прочита.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Апликација \"%s\" не може бити инсталирана јер није компатибилна са овом верзијом сервера.", + "__language_name__" : "Српски", + "This is an automatically sent email, please do not reply." : "Ово је аутоматски генерисана порука, не одговарајте на њу.", + "Help" : "Помоћ", + "Apps" : "Апликације", + "Settings" : "Поставке", + "Log out" : "Одјава", + "Users" : "Корисници", + "Unknown user" : "Непознат корисник", + "Additional settings" : "Додатне поставке", + "%s enter the database username and name." : "%s унеси корисничко име базе података и име.", + "%s enter the database username." : "%s унеси корисничко име базе података.", + "%s enter the database name." : "%s унеси име базе података.", + "%s you may not use dots in the database name" : "%s не можете користити тачке у имену базе података", + "MySQL username and/or password not valid" : "MySQL корисничко име и/или лозинка нису исправни", + "You need to enter details of an existing account." : "Потребно је да унесете детаље постојећег налога.", + "Oracle connection could not be established" : "Веза са базом података Oracle не може бити успостављена", + "Oracle username and/or password not valid" : "Oracle корисничко име и/или лозинка нису исправни", + "PostgreSQL username and/or password not valid" : "PostgreSQL корисничко име и/или лозинка нису исправни", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Мек ОС Икс није подржан и %s неће радити исправно на овој платформи. Користите га на сопствени ризик!", + "For the best results, please consider using a GNU/Linux server instead." : "За најбоље резултате, размотрите употребу ГНУ/Линукс сервера.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Изгледа да %s ради у 32-битном PHP окружењу а open_basedir је подешен у php.ini фајлу. То може довести до проблема са фајловима већим од 4 GB, те стога није препоручљиво.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Уклоните open_basedir поставку из php.ini фајла или пређите на 64-битни PHP.", + "Set an admin username." : "Поставите име за администратора.", + "Set an admin password." : "Поставите лозинку за администратора.", + "Can't create or write into the data directory %s" : "Не могу креирати или уписивати у директоријум података %s", + "Invalid Federated Cloud ID" : "Неисправан ИД Здруженог облака", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Позадина дељења %s мора користити корисничко окружење OCP\\Share_Backend", + "Sharing backend %s not found" : "Позадина за дељење %s није пронађена", + "Sharing backend for %s not found" : "Позадина за дељење за %s није пронађена", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s је поделио „%2$s“ са Вама и жели да дода:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s је поделио „%2$s“ са Вама и жели да дода", + "»%s« added a note to a file shared with you" : "\"%s\" је додао белешку на фајл који дели са Вама", + "Open »%s«" : "Отвори „%s“", + "%1$s via %2$s" : "%1$s преко %2$s", + "You are not allowed to share %s" : "Није вам дозвољено да делите %s", + "Can’t increase permissions of %s" : "Не могу да повећам дозволе за %s", + "Files can’t be shared with delete permissions" : "Фајлови не могу бити дељени са дозволама за брисање", + "Files can’t be shared with create permissions" : "Фајлови не могу бити дељени са дозволама за креирање", + "Expiration date is in the past" : "Датум истека је у прошлости", + "Can’t set expiration date more than %s days in the future" : "Не могу да поставим датум истека више од %s дана у будућност", + "%1$s shared »%2$s« with you" : "%1$s је поделио „%2$s“ са Вама", + "%1$s shared »%2$s« with you." : "%1$s је поделио „%2$s“ са Вама.", + "Click the button below to open it." : "Кликните дугме испод да га отворите.", + "The requested share does not exist anymore" : "Захтевано дељење више не постоји", + "Could not find category \"%s\"" : "Не могу да пронађем категорију „%s“.", + "Sunday" : "Недеља", + "Monday" : "Понедељак", + "Tuesday" : "Уторак", + "Wednesday" : "Среда", + "Thursday" : "Четвртак", + "Friday" : "Петак", + "Saturday" : "Субота", + "Sun." : "Нед", + "Mon." : "Пон", + "Tue." : "Уто", + "Wed." : "Сре", + "Thu." : "Чет", + "Fri." : "Пет", + "Sat." : "Суб", + "Su" : "Не", + "Mo" : "По", + "Tu" : "Ут", + "We" : "Ср", + "Th" : "Че", + "Fr" : "Пе", + "Sa" : "Су", + "January" : "Јануар", + "February" : "Фебруар", + "March" : "Март", + "April" : "Април", + "May" : "Мај", + "June" : "Јун", + "July" : "Јул", + "August" : "Август", + "September" : "Септембар", + "October" : "Октобар", + "November" : "Новембар", + "December" : "Децембар", + "Jan." : "Јан.", + "Feb." : "Феб.", + "Mar." : "Мар.", + "Apr." : "Апр.", + "May." : "Мај.", + "Jun." : "Јун.", + "Jul." : "Јул.", + "Aug." : "Авг.", + "Sep." : "Сеп.", + "Oct." : "Окт.", + "Nov." : "Нов.", + "Dec." : "Дец.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "У корисничком имену су дозвољени само следећи карактери: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Морате унети исправно корисничко име", + "Username contains whitespace at the beginning or at the end" : "Корисничко име садржи белине на почетку или на крају", + "Username must not consist of dots only" : "Корисничко име не могу бити само тачке", + "Username is invalid because files already exist for this user" : "Корисничко име није исправно пошто већ постоје фајлови за овог корисника", + "A valid password must be provided" : "Морате унети исправну лозинку", + "The username is already being used" : "Корисничко име се већ користи", + "Could not create user" : "Не могу да направим корисника", + "User disabled" : "Корисник онемогућен", + "Login canceled by app" : "Пријава отказана од стране апликације", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Апликација „%1$s“ не може бити инсталирана јер следеће зависности нису испуњене: %2$s", + "a safe home for all your data" : "сигурно место за све Ваше податке", + "File is currently busy, please try again later" : "Фајл је тренутно заузет, покушајте поново касније", + "Can't read file" : "Не могу да читам фајл", + "Application is not enabled" : "Апликација није укључена", + "Authentication error" : "Грешка при провери идентитета", + "Token expired. Please reload page." : "Жетон је истекао. Поново учитајте страницу.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Нема драјвера базе података (скулајт, мајскул или постгрескул).", + "Cannot write into \"config\" directory" : "Не могу уписивати у директоријуму „config“", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Ово се обично може средити давањем права писања веб серверу за директоријум са подешавањима. Погледајте %s", + "Cannot write into \"apps\" directory" : "Не могу уписивати у директоријуму „apps“", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Ово се обично може поправити давањем права уписа веб серверу директоријум апликација или искуључивањем продавнице апликација у фајлу config file.", + "Cannot create \"data\" directory" : "Не могу да направим \"data\" директоријум", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Ово се обично може поправити тако што веб серверу дате право уписа за корени директоријуму. Видети %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Привилегије се обично могу поправити тако што веб серверу дате право уписа за корени директоријуму. Видети %s", + "Setting locale to %s failed" : "Постављање локалитета на %s није успело", + "Please install one of these locales on your system and restart your webserver." : "Инсталирајте неки од ових локалитета на ваш систем и поново покрените веб сервер.", + "PHP module %s not installed." : "PHP модул %s није инсталиран.", + "Please ask your server administrator to install the module." : "Замолите администратора вашег сервера да инсталира тај модул.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP поставка „%s“ није постављена на „%s“.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Некстклауд ће прорадити поново када прилагодите ово подашавање у php.ini фајлу", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload је постављено на „%s“ уместо на очекивану вредност „0“", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Да би решили овај проблем поставите mbstring.func_overload на 0 у фајлу php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Потребан је бар libxml2 2.7.0. Тренутно је инсталиран %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Да поправите овај проблем, ажурирајте верзију библиотеке libxml2 и рестартујте веб сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP је очигледно подешен да склања уметнуте doc блокове. То ће учинити неколико кључних апликација недоступним.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ово је вероватно изазвано кешом или акцелератором као што су ЗендОПкеш или еАкцелератор.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP модули су инсталирани али се и даље воде као недостајући?", + "Please ask your server administrator to restart the web server." : "Замолите вашег администратора сервера да поново покрене веб сервер.", + "PostgreSQL >= 9 required" : "Захтеван је ПостгреСкул >= 9", + "Please upgrade your database version" : "Надоградите ваше издање базе", + "Your data directory is readable by other users" : "Директоријум са подацима је читљив од стране других корисника система", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Промените дозволе у 0770 како директоријуми не би могли бити излистани од стране других корисника.", + "Your data directory must be an absolute path" : "Директоријум са подацима мора бити апсолутна путања", + "Check the value of \"datadirectory\" in your configuration" : "Проверите податак за \"datadirectory\" у вашој конфигурацији", + "Your data directory is invalid" : "Директоријум са подацима није исправан", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Уверите се да фајл \".ocdata\" постоји у корену директоријума са подацима.", + "Action \"%s\" not supported or implemented." : "Радња \"%s\" није подржана или имплементирана.", + "Authentication failed, wrong token or provider ID given" : "Неуспела провера идентитета, добијен погрешан токен или ID провајдера", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Недостају параметри да би се довршио захтев. Недостајући параметри: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "Идентификацију \"%1$s\" већ користи провајдер здруживања \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Провајдер клауд здруживања са ID-ом \"%s\" не постоји", + "Could not obtain lock type %d on \"%s\"." : "Не могу да остварим закључаност %d за „%s“.", + "Storage unauthorized. %s" : "Складиште није овлашћено. %s", + "Storage incomplete configuration. %s" : "Непотпуна конфигурација складишта. %s", + "Storage connection error. %s" : "Грешка приликом повезивања на складиште. %s", + "Storage is temporarily not available" : "Складиште привремено није доступно", + "Storage connection timeout. %s" : "Истекло је време за повезивање на складиште. %s", + "Following databases are supported: %s" : "Подржане су следеће базе података: %s", + "Following platforms are supported: %s" : "Подржане су следеће платформе: %s", + "Overview" : "Преглед", + "Basic settings" : "Основне поставке", + "Sharing" : "Дељење", + "Security" : "Безбедност", + "Groupware" : "Радни тимови", + "Personal info" : "Лични подаци", + "Mobile & desktop" : "Мобилни и десктоп", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Ово се обично може поправити тако што веб серверу дате приступ уписа за директоријум где су апликације или тако што онемогућите продавницу у config фајлу. Видети %s" +},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sr@latin.js b/docker/overlays/nextcloud/html/lib/l10n/sr@latin.js new file mode 100644 index 0000000..8527fe5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sr@latin.js @@ -0,0 +1,18 @@ +OC.L10N.register( + "lib", + { + "seconds ago" : "pre par sekundi", + "__language_name__" : "Srpski", + "Help" : "Help", + "Settings" : "Postavke", + "Log out" : "Odjava", + "Sunday" : "Nedelja", + "Monday" : "Ponedeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Sreda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sharing" : "Deljenje" +}, +"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sr@latin.json b/docker/overlays/nextcloud/html/lib/l10n/sr@latin.json new file mode 100644 index 0000000..ad8a29c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sr@latin.json @@ -0,0 +1,16 @@ +{ "translations": { + "seconds ago" : "pre par sekundi", + "__language_name__" : "Srpski", + "Help" : "Help", + "Settings" : "Postavke", + "Log out" : "Odjava", + "Sunday" : "Nedelja", + "Monday" : "Ponedeljak", + "Tuesday" : "Utorak", + "Wednesday" : "Sreda", + "Thursday" : "Četvrtak", + "Friday" : "Petak", + "Saturday" : "Subota", + "Sharing" : "Deljenje" +},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/sv.js b/docker/overlays/nextcloud/html/lib/l10n/sv.js new file mode 100644 index 0000000..ad03a9f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sv.js @@ -0,0 +1,238 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Kan inte skriva till \"config\" katalogen!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Detta kan vanligtvis åtgärdas genom att ge webservern skrivåtkomst till config-katalogen", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den.", + "See %s" : "Se %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Detta kan vanligtvis åtgärdas genom att ge webbservern skrivåtkomst till config-katalogen.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den. Se %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.", + "Sample configuration detected" : "Exempel-konfiguration detekterad", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Vänligen läs dokumentationen innan du utför ändringar på config.php", + "Other activities" : "Andra aktiviteter", + "%1$s and %2$s" : "%1$s och %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s och %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s och %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s och %5$s", + "Education Edition" : "Utbildningspaket", + "Enterprise bundle" : "Företagspaketet", + "Groupware bundle" : "Gruppvarupaket", + "Social sharing bundle" : "Kommunikationspaket", + "PHP %s or higher is required." : "PHP %s eller högre krävs.", + "PHP with a version lower than %s is required." : "PHP med version lägre än %s krävs.", + "%sbit or higher PHP required." : "%sbit eller nyare PHP-version krävs.", + "The following databases are supported: %s" : "Följande databaser stöds: %s", + "The command line tool %s could not be found" : "Kommandoradsverktyget %s hittades inte.", + "The library %s is not available." : "Biblioteket %s är inte tillgängligt.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Bibliotek %1$s med en högre version än %2$s krävs - tillgänglig version %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Bibliotek %1$s med en lägre version än %2$s krävs - tillgänglig version %3$s.", + "The following platforms are supported: %s" : "Följande plattformar stöds: %s", + "Server version %s or higher is required." : "Serverversion %s eller nyare krävs.", + "Server version %s or lower is required." : "Serverversion %s eller äldre krävs.", + "Logged in user must be an admin or sub admin" : "Inloggad användare måste vara administratör eller del-administratör", + "Logged in user must be an admin" : "Inloggad användare måste vara administratör", + "Wiping of device %s has started" : "Rensning av enhet %s har startat", + "Wiping of device »%s« has started" : "Rensning av enhet »%s« har startat", + "»%s« started remote wipe" : "»%s« startade fjärrensning", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Enhet eller applikation »%s« har startat fjärrensning. Du kommer att få ett nytt mail när processen är klar", + "Wiping of device %s has finished" : "Rensning av enhet %s har slutförts", + "Wiping of device »%s« has finished" : "Rensning av enhet »%s« har slutförts", + "»%s« finished remote wipe" : "»%s« slutförde fjärrensning", + "Device or application »%s« has finished the remote wipe process." : "Enhet eller applikation »%s« har slutfört fjärrensning.", + "Remote wipe started" : "Fjärrensning startad", + "A remote wipe was started on device %s" : "Fjärrensning startades på enhet %s", + "Remote wipe finished" : "Fjärrensning klar", + "The remote wipe on %s has finished" : "Fjärrensning av enhet %s har slutörts", + "Authentication" : "Autentisering", + "Unknown filetype" : "Okänd filtyp", + "Invalid image" : "Ogiltig bild", + "Avatar image is not square" : "Profilbilden är inte fyrkantig", + "today" : "idag", + "tomorrow" : "imorgon", + "yesterday" : "igår", + "_in %n day_::_in %n days_" : ["om %n dag","om %n dagar"], + "_%n day ago_::_%n days ago_" : ["%n dag sedan","%n dagar sedan"], + "next month" : "nästa månad", + "last month" : "förra månaden", + "_in %n month_::_in %n months_" : ["om %n månad","om %n månader"], + "_%n month ago_::_%n months ago_" : ["%n månad sedan","%n månader sedan"], + "next year" : "nästa år", + "last year" : "förra året", + "_in %n year_::_in %n years_" : ["om %n år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år sedan","%n år sedan"], + "_in %n hour_::_in %n hours_" : ["om %n timme","om %n timmar"], + "_%n hour ago_::_%n hours ago_" : ["%n timme sedan","%n timmar sedan"], + "_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minuter"], + "_%n minute ago_::_%n minutes ago_" : ["%n minut sedan","%n minuter sedan"], + "in a few seconds" : "om några sekunder", + "seconds ago" : "sekunder sedan", + "Empty file" : "Tom fil", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul med ID: %s finns inte längre. Vänligen aktivera det i dina appinställningar eller kontakta din administratör.", + "File name is a reserved word" : "Filnamnet är ett reserverat ord", + "File name contains at least one invalid character" : "Filnamnet innehåller minst ett ogiltigt tecken", + "File name is too long" : "Filnamnet är för långt", + "Dot files are not allowed" : "Dot-filer är inte tillåtna", + "Empty filename is not allowed" : "Tomma filnamn är inte tillåtna", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan inte installeras eftersom appinfo-filen inte kan läsas.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Appen \"%s\" kan inte installeras eftersom den inte är kompatibel med den här versionen av servern.", + "__language_name__" : "Svenska", + "This is an automatically sent email, please do not reply." : "Detta är ett automatiskt skickat e-postmeddelande, svara inte på detta meddelande.", + "Help" : "Hjälp", + "Apps" : "Appar", + "Settings" : "Inställningar", + "Log out" : "Logga ut", + "Users" : "Användare", + "Unknown user" : "Okänd användare", + "Additional settings" : "Övriga inställningar", + "%s enter the database username and name." : "%s ange användarnamn och namn för databasen.", + "%s enter the database username." : "%s ange databasanvändare.", + "%s enter the database name." : "%s ange databasnamn", + "%s you may not use dots in the database name" : "%s du får inte använda punkter i databasnamnet", + "MySQL username and/or password not valid" : "MySQL-användarnamn och/eller lösenord är felaktigt", + "You need to enter details of an existing account." : "Du måste ange inloggningsuppgifter av ett aktuellt konto.", + "Oracle connection could not be established" : "Oracle-anslutning kunde inte etableras", + "Oracle username and/or password not valid" : "Oracle-användarnamnet och/eller lösenordet är felaktigt", + "PostgreSQL username and/or password not valid" : "PostgreSQL-användarnamnet och/eller lösenordet är felaktigt", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X stöds inte och %s kommer inte att fungera korrekt på denna plattform. Använd på egen risk!", + "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, vänligen överväg att använda en GNU/Linux-server istället.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det verkar som om denna %s instans körs på en 32-bitars PHP miljö och open_basedir har konfigurerats i php.ini. Detta kommer att leda till problem med filer över 4 GB och är verkligen inte rekommenderat!", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Vänligen ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", + "Set an admin username." : "Ange ett användarnamn för administratören.", + "Set an admin password." : "Ange ett administratörslösenord.", + "Can't create or write into the data directory %s" : "Kan inte skapa eller skriva till data-katalogen %s", + "Invalid Federated Cloud ID" : "Ogiltigt federerat moln-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delningsgränssnittet %s måste implementera gränssnittet OCP\\Share_Backend", + "Sharing backend %s not found" : "Delningsgränssnittet %s hittades inte", + "Sharing backend for %s not found" : "Delningsgränssnittet för %s hittades inte", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s delade »%2$s« med dig och vill lägga till:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s delade »%2$s« med dig och vill lägga till", + "»%s« added a note to a file shared with you" : "»%s« la till en kommentar till en fil delad med dig", + "Open »%s«" : "Öppna »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har inte rätt att dela %s", + "Can’t increase permissions of %s" : "Kan inte öka rättigheterna för %s", + "Files can’t be shared with delete permissions" : "Filer kan inte delas med borttagningsrättigheter", + "Files can’t be shared with create permissions" : "Filer kan inte delas med rättigheter att skapa", + "Expiration date is in the past" : "Utgångsdatum är i det förflutna", + "Can’t set expiration date more than %s days in the future" : "Kan inte sätta utgångsdatum mer än %s dagar framåt", + "%1$s shared »%2$s« with you" : "%1$s delade »%2$s« med dig", + "%1$s shared »%2$s« with you." : "%1$s delade »%2$s« med dig.", + "Click the button below to open it." : "Klicka på knappen nedan för att öppna det.", + "The requested share does not exist anymore" : "Den begärda delningen finns inte mer", + "Could not find category \"%s\"" : "Kunde inte hitta kategorin \"%s\"", + "Sunday" : "Söndag", + "Monday" : "Måndag", + "Tuesday" : "Tisdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lördag", + "Sun." : "Sön.", + "Mon." : "Mån.", + "Tue." : "Tis.", + "Wed." : "Ons.", + "Thu." : "Tors.", + "Fri." : "Fre.", + "Sat." : "Lör.", + "Su" : "Sö", + "Mo" : "Må", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lö", + "January" : "Januari", + "February" : "Februari", + "March" : "Mars", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "Augusti", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Endast följande tecken är tillåtna i användarnamnet: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Ett giltigt användarnamn måste anges", + "Username contains whitespace at the beginning or at the end" : "Användarnamnet består av ett mellanslag i början eller i slutet", + "Username must not consist of dots only" : "Användarnamnet får inte innehålla enbart punkter", + "Username is invalid because files already exist for this user" : "Användarnamnet är ogiltigt eftersom det redan finns filer för den här användaren", + "A valid password must be provided" : "Ett giltigt lösenord måste anges", + "The username is already being used" : "Användarnamnet används redan", + "Could not create user" : "Kunde inte skapa användare", + "User disabled" : "Användare inaktiverad", + "Login canceled by app" : "Inloggningen avbruten av appen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Appen \"%1$s\" kan inte installeras eftersom följande beroenden inte är uppfyllda: %2$s", + "a safe home for all your data" : "ett säkert hem för alla dina data", + "File is currently busy, please try again later" : "Filen är för tillfället upptagen, vänligen försök igen senare", + "Can't read file" : "Kan inte läsa filen", + "Application is not enabled" : "Applikationen är inte aktiverad", + "Authentication error" : "Fel vid autentisering", + "Token expired. Please reload page." : "Token har löpt ut. Vänligen uppdatera sidan.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Inga databasdrivrutiner (sqlite, mysql, eller postgresql) installerade.", + "Cannot write into \"config\" directory" : "Kan inte skriva till \"config\" katalogen", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till konfigureringsmappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan inte skriva till \"apps\" katalogen!", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Detta kan vanligtvis åtgärdas genom att ge webbservern skrivrättigheter till applikationskatalogen eller stänga av app-butiken i konfigurationsfilen.", + "Cannot create \"data\" directory" : "Kan inte skapa \"data\"-mapp", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rättigheter kan vanligtvis fixas genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s.", + "Setting locale to %s failed" : "Sätta locale till %s misslyckades", + "Please install one of these locales on your system and restart your webserver." : "Vänligen Installera en av dessa språk på ditt system och starta om webbservern.", + "PHP module %s not installed." : "PHP-modulen %s är inte installerad.", + "Please ask your server administrator to install the module." : "Vänligen be serveradministratören att installera modulen.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-inställning \"%s\" är inte inställd på \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Att ändra denna inställning i php.ini kommer göra så att Nextcloud fungerar igen", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload är satt till \"%s\" istället för det förväntade värdet \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "För att åtgärda detta problem sätt värdet mbstring.func_overload till 0 i din php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.", + "To fix this issue update your libxml2 version and restart your web server." : "För att åtgärda detta problem uppdatera libxml2 versionen och starta om din webbserver.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP är tydligen inställt för att tömma \"inline doc blocks\". Detta kommer att göra flera kärnprogram otillgängliga.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Detta orsakas troligtvis av en cache/accelerator som t ex Zend OPchache eller eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har installerats, men de listas fortfarande som saknade?", + "Please ask your server administrator to restart the web server." : "Vänligen be din serveradministratör att starta om webbservern.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 krävs", + "Please upgrade your database version" : "Vänligen uppgradera din databasversion", + "Your data directory is readable by other users" : "Din datamapp är läsbar av andra användare", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Vänligen ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", + "Your data directory must be an absolute path" : "Du måste specificera en korrekt sökväg till datamappen", + "Check the value of \"datadirectory\" in your configuration" : "Kontrollera värdet av \"datakatalog\" i din konfiguration", + "Your data directory is invalid" : "Din datamapp är ogiltig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Säkerställ att du har filen \".ocdata\" i huvudkatalogen för dina data.", + "Action \"%s\" not supported or implemented." : "Åtgärd \"%s\" stöds inte eller är inte implementerad.", + "Authentication failed, wrong token or provider ID given" : "Autentisering misslyckades, felaktig token eller leverantörs-ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametrar saknas för att slutföra förfrågan. Saknade parametrar: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" används redan av moln - federationsleverantör \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Moln - federationsleverantör med ID: \"%s\" finns inte.", + "Could not obtain lock type %d on \"%s\"." : "Kunde inte hämta låstyp %d på \"%s\".", + "Storage unauthorized. %s" : "Lagring obehörig. %s", + "Storage incomplete configuration. %s" : "Lagringsutrymme felaktigt inställt. %s", + "Storage connection error. %s" : "Lagringsutrymme lyckas inte ansluta. %s", + "Storage is temporarily not available" : "Lagringsutrymme är för tillfället inte tillgängligt", + "Storage connection timeout. %s" : "Lagringsutrymme lyckas inte ansluta \"timeout\". %s", + "Following databases are supported: %s" : "Följande databastyper stöds: %s", + "Following platforms are supported: %s" : "Följande plattformar stöds: %s", + "Overview" : "Översikt", + "Basic settings" : "Generella inställningar", + "Sharing" : "Delning", + "Security" : "Säkerhet", + "Groupware" : "Grupprogram", + "Personal info" : "Personlig information", + "Mobile & desktop" : "Mobil & skrivbord", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Detta kan vanligtvis lösas genom att ge webbservern skrivåtkomst till mappen för appar eller genom att inaktivera appbutiken i konfigurationsfilen. Se %s" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/sv.json b/docker/overlays/nextcloud/html/lib/l10n/sv.json new file mode 100644 index 0000000..2ab8460 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/sv.json @@ -0,0 +1,236 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Kan inte skriva till \"config\" katalogen!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Detta kan vanligtvis åtgärdas genom att ge webservern skrivåtkomst till config-katalogen", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den.", + "See %s" : "Se %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Detta kan vanligtvis åtgärdas genom att ge webbservern skrivåtkomst till config-katalogen.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den. Se %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.", + "Sample configuration detected" : "Exempel-konfiguration detekterad", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Vänligen läs dokumentationen innan du utför ändringar på config.php", + "Other activities" : "Andra aktiviteter", + "%1$s and %2$s" : "%1$s och %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s och %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s och %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s och %5$s", + "Education Edition" : "Utbildningspaket", + "Enterprise bundle" : "Företagspaketet", + "Groupware bundle" : "Gruppvarupaket", + "Social sharing bundle" : "Kommunikationspaket", + "PHP %s or higher is required." : "PHP %s eller högre krävs.", + "PHP with a version lower than %s is required." : "PHP med version lägre än %s krävs.", + "%sbit or higher PHP required." : "%sbit eller nyare PHP-version krävs.", + "The following databases are supported: %s" : "Följande databaser stöds: %s", + "The command line tool %s could not be found" : "Kommandoradsverktyget %s hittades inte.", + "The library %s is not available." : "Biblioteket %s är inte tillgängligt.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Bibliotek %1$s med en högre version än %2$s krävs - tillgänglig version %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Bibliotek %1$s med en lägre version än %2$s krävs - tillgänglig version %3$s.", + "The following platforms are supported: %s" : "Följande plattformar stöds: %s", + "Server version %s or higher is required." : "Serverversion %s eller nyare krävs.", + "Server version %s or lower is required." : "Serverversion %s eller äldre krävs.", + "Logged in user must be an admin or sub admin" : "Inloggad användare måste vara administratör eller del-administratör", + "Logged in user must be an admin" : "Inloggad användare måste vara administratör", + "Wiping of device %s has started" : "Rensning av enhet %s har startat", + "Wiping of device »%s« has started" : "Rensning av enhet »%s« har startat", + "»%s« started remote wipe" : "»%s« startade fjärrensning", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Enhet eller applikation »%s« har startat fjärrensning. Du kommer att få ett nytt mail när processen är klar", + "Wiping of device %s has finished" : "Rensning av enhet %s har slutförts", + "Wiping of device »%s« has finished" : "Rensning av enhet »%s« har slutförts", + "»%s« finished remote wipe" : "»%s« slutförde fjärrensning", + "Device or application »%s« has finished the remote wipe process." : "Enhet eller applikation »%s« har slutfört fjärrensning.", + "Remote wipe started" : "Fjärrensning startad", + "A remote wipe was started on device %s" : "Fjärrensning startades på enhet %s", + "Remote wipe finished" : "Fjärrensning klar", + "The remote wipe on %s has finished" : "Fjärrensning av enhet %s har slutörts", + "Authentication" : "Autentisering", + "Unknown filetype" : "Okänd filtyp", + "Invalid image" : "Ogiltig bild", + "Avatar image is not square" : "Profilbilden är inte fyrkantig", + "today" : "idag", + "tomorrow" : "imorgon", + "yesterday" : "igår", + "_in %n day_::_in %n days_" : ["om %n dag","om %n dagar"], + "_%n day ago_::_%n days ago_" : ["%n dag sedan","%n dagar sedan"], + "next month" : "nästa månad", + "last month" : "förra månaden", + "_in %n month_::_in %n months_" : ["om %n månad","om %n månader"], + "_%n month ago_::_%n months ago_" : ["%n månad sedan","%n månader sedan"], + "next year" : "nästa år", + "last year" : "förra året", + "_in %n year_::_in %n years_" : ["om %n år","om %n år"], + "_%n year ago_::_%n years ago_" : ["%n år sedan","%n år sedan"], + "_in %n hour_::_in %n hours_" : ["om %n timme","om %n timmar"], + "_%n hour ago_::_%n hours ago_" : ["%n timme sedan","%n timmar sedan"], + "_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minuter"], + "_%n minute ago_::_%n minutes ago_" : ["%n minut sedan","%n minuter sedan"], + "in a few seconds" : "om några sekunder", + "seconds ago" : "sekunder sedan", + "Empty file" : "Tom fil", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul med ID: %s finns inte längre. Vänligen aktivera det i dina appinställningar eller kontakta din administratör.", + "File name is a reserved word" : "Filnamnet är ett reserverat ord", + "File name contains at least one invalid character" : "Filnamnet innehåller minst ett ogiltigt tecken", + "File name is too long" : "Filnamnet är för långt", + "Dot files are not allowed" : "Dot-filer är inte tillåtna", + "Empty filename is not allowed" : "Tomma filnamn är inte tillåtna", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Appen \"%s\" kan inte installeras eftersom appinfo-filen inte kan läsas.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Appen \"%s\" kan inte installeras eftersom den inte är kompatibel med den här versionen av servern.", + "__language_name__" : "Svenska", + "This is an automatically sent email, please do not reply." : "Detta är ett automatiskt skickat e-postmeddelande, svara inte på detta meddelande.", + "Help" : "Hjälp", + "Apps" : "Appar", + "Settings" : "Inställningar", + "Log out" : "Logga ut", + "Users" : "Användare", + "Unknown user" : "Okänd användare", + "Additional settings" : "Övriga inställningar", + "%s enter the database username and name." : "%s ange användarnamn och namn för databasen.", + "%s enter the database username." : "%s ange databasanvändare.", + "%s enter the database name." : "%s ange databasnamn", + "%s you may not use dots in the database name" : "%s du får inte använda punkter i databasnamnet", + "MySQL username and/or password not valid" : "MySQL-användarnamn och/eller lösenord är felaktigt", + "You need to enter details of an existing account." : "Du måste ange inloggningsuppgifter av ett aktuellt konto.", + "Oracle connection could not be established" : "Oracle-anslutning kunde inte etableras", + "Oracle username and/or password not valid" : "Oracle-användarnamnet och/eller lösenordet är felaktigt", + "PostgreSQL username and/or password not valid" : "PostgreSQL-användarnamnet och/eller lösenordet är felaktigt", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X stöds inte och %s kommer inte att fungera korrekt på denna plattform. Använd på egen risk!", + "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, vänligen överväg att använda en GNU/Linux-server istället.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det verkar som om denna %s instans körs på en 32-bitars PHP miljö och open_basedir har konfigurerats i php.ini. Detta kommer att leda till problem med filer över 4 GB och är verkligen inte rekommenderat!", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Vänligen ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", + "Set an admin username." : "Ange ett användarnamn för administratören.", + "Set an admin password." : "Ange ett administratörslösenord.", + "Can't create or write into the data directory %s" : "Kan inte skapa eller skriva till data-katalogen %s", + "Invalid Federated Cloud ID" : "Ogiltigt federerat moln-ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Delningsgränssnittet %s måste implementera gränssnittet OCP\\Share_Backend", + "Sharing backend %s not found" : "Delningsgränssnittet %s hittades inte", + "Sharing backend for %s not found" : "Delningsgränssnittet för %s hittades inte", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s delade »%2$s« med dig och vill lägga till:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s delade »%2$s« med dig och vill lägga till", + "»%s« added a note to a file shared with you" : "»%s« la till en kommentar till en fil delad med dig", + "Open »%s«" : "Öppna »%s«", + "%1$s via %2$s" : "%1$s via %2$s", + "You are not allowed to share %s" : "Du har inte rätt att dela %s", + "Can’t increase permissions of %s" : "Kan inte öka rättigheterna för %s", + "Files can’t be shared with delete permissions" : "Filer kan inte delas med borttagningsrättigheter", + "Files can’t be shared with create permissions" : "Filer kan inte delas med rättigheter att skapa", + "Expiration date is in the past" : "Utgångsdatum är i det förflutna", + "Can’t set expiration date more than %s days in the future" : "Kan inte sätta utgångsdatum mer än %s dagar framåt", + "%1$s shared »%2$s« with you" : "%1$s delade »%2$s« med dig", + "%1$s shared »%2$s« with you." : "%1$s delade »%2$s« med dig.", + "Click the button below to open it." : "Klicka på knappen nedan för att öppna det.", + "The requested share does not exist anymore" : "Den begärda delningen finns inte mer", + "Could not find category \"%s\"" : "Kunde inte hitta kategorin \"%s\"", + "Sunday" : "Söndag", + "Monday" : "Måndag", + "Tuesday" : "Tisdag", + "Wednesday" : "Onsdag", + "Thursday" : "Torsdag", + "Friday" : "Fredag", + "Saturday" : "Lördag", + "Sun." : "Sön.", + "Mon." : "Mån.", + "Tue." : "Tis.", + "Wed." : "Ons.", + "Thu." : "Tors.", + "Fri." : "Fre.", + "Sat." : "Lör.", + "Su" : "Sö", + "Mo" : "Må", + "Tu" : "Ti", + "We" : "On", + "Th" : "To", + "Fr" : "Fr", + "Sa" : "Lö", + "January" : "Januari", + "February" : "Februari", + "March" : "Mars", + "April" : "April", + "May" : "Maj", + "June" : "Juni", + "July" : "Juli", + "August" : "Augusti", + "September" : "September", + "October" : "Oktober", + "November" : "November", + "December" : "December", + "Jan." : "Jan.", + "Feb." : "Feb.", + "Mar." : "Mar.", + "Apr." : "Apr.", + "May." : "Maj.", + "Jun." : "Jun.", + "Jul." : "Jul.", + "Aug." : "Aug.", + "Sep." : "Sep.", + "Oct." : "Okt.", + "Nov." : "Nov.", + "Dec." : "Dec.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Endast följande tecken är tillåtna i användarnamnet: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"", + "A valid username must be provided" : "Ett giltigt användarnamn måste anges", + "Username contains whitespace at the beginning or at the end" : "Användarnamnet består av ett mellanslag i början eller i slutet", + "Username must not consist of dots only" : "Användarnamnet får inte innehålla enbart punkter", + "Username is invalid because files already exist for this user" : "Användarnamnet är ogiltigt eftersom det redan finns filer för den här användaren", + "A valid password must be provided" : "Ett giltigt lösenord måste anges", + "The username is already being used" : "Användarnamnet används redan", + "Could not create user" : "Kunde inte skapa användare", + "User disabled" : "Användare inaktiverad", + "Login canceled by app" : "Inloggningen avbruten av appen", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Appen \"%1$s\" kan inte installeras eftersom följande beroenden inte är uppfyllda: %2$s", + "a safe home for all your data" : "ett säkert hem för alla dina data", + "File is currently busy, please try again later" : "Filen är för tillfället upptagen, vänligen försök igen senare", + "Can't read file" : "Kan inte läsa filen", + "Application is not enabled" : "Applikationen är inte aktiverad", + "Authentication error" : "Fel vid autentisering", + "Token expired. Please reload page." : "Token har löpt ut. Vänligen uppdatera sidan.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Inga databasdrivrutiner (sqlite, mysql, eller postgresql) installerade.", + "Cannot write into \"config\" directory" : "Kan inte skriva till \"config\" katalogen", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till konfigureringsmappen. Se %s", + "Cannot write into \"apps\" directory" : "Kan inte skriva till \"apps\" katalogen!", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Detta kan vanligtvis åtgärdas genom att ge webbservern skrivrättigheter till applikationskatalogen eller stänga av app-butiken i konfigurationsfilen.", + "Cannot create \"data\" directory" : "Kan inte skapa \"data\"-mapp", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rättigheter kan vanligtvis fixas genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s.", + "Setting locale to %s failed" : "Sätta locale till %s misslyckades", + "Please install one of these locales on your system and restart your webserver." : "Vänligen Installera en av dessa språk på ditt system och starta om webbservern.", + "PHP module %s not installed." : "PHP-modulen %s är inte installerad.", + "Please ask your server administrator to install the module." : "Vänligen be serveradministratören att installera modulen.", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP-inställning \"%s\" är inte inställd på \"%s\".", + "Adjusting this setting in php.ini will make Nextcloud run again" : "Att ändra denna inställning i php.ini kommer göra så att Nextcloud fungerar igen", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload är satt till \"%s\" istället för det förväntade värdet \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "För att åtgärda detta problem sätt värdet mbstring.func_overload till 0 i din php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.", + "To fix this issue update your libxml2 version and restart your web server." : "För att åtgärda detta problem uppdatera libxml2 versionen och starta om din webbserver.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP är tydligen inställt för att tömma \"inline doc blocks\". Detta kommer att göra flera kärnprogram otillgängliga.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Detta orsakas troligtvis av en cache/accelerator som t ex Zend OPchache eller eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har installerats, men de listas fortfarande som saknade?", + "Please ask your server administrator to restart the web server." : "Vänligen be din serveradministratör att starta om webbservern.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 krävs", + "Please upgrade your database version" : "Vänligen uppgradera din databasversion", + "Your data directory is readable by other users" : "Din datamapp är läsbar av andra användare", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Vänligen ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", + "Your data directory must be an absolute path" : "Du måste specificera en korrekt sökväg till datamappen", + "Check the value of \"datadirectory\" in your configuration" : "Kontrollera värdet av \"datakatalog\" i din konfiguration", + "Your data directory is invalid" : "Din datamapp är ogiltig", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Säkerställ att du har filen \".ocdata\" i huvudkatalogen för dina data.", + "Action \"%s\" not supported or implemented." : "Åtgärd \"%s\" stöds inte eller är inte implementerad.", + "Authentication failed, wrong token or provider ID given" : "Autentisering misslyckades, felaktig token eller leverantörs-ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametrar saknas för att slutföra förfrågan. Saknade parametrar: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" används redan av moln - federationsleverantör \"%2$s\"", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "Moln - federationsleverantör med ID: \"%s\" finns inte.", + "Could not obtain lock type %d on \"%s\"." : "Kunde inte hämta låstyp %d på \"%s\".", + "Storage unauthorized. %s" : "Lagring obehörig. %s", + "Storage incomplete configuration. %s" : "Lagringsutrymme felaktigt inställt. %s", + "Storage connection error. %s" : "Lagringsutrymme lyckas inte ansluta. %s", + "Storage is temporarily not available" : "Lagringsutrymme är för tillfället inte tillgängligt", + "Storage connection timeout. %s" : "Lagringsutrymme lyckas inte ansluta \"timeout\". %s", + "Following databases are supported: %s" : "Följande databastyper stöds: %s", + "Following platforms are supported: %s" : "Följande plattformar stöds: %s", + "Overview" : "Översikt", + "Basic settings" : "Generella inställningar", + "Sharing" : "Delning", + "Security" : "Säkerhet", + "Groupware" : "Grupprogram", + "Personal info" : "Personlig information", + "Mobile & desktop" : "Mobil & skrivbord", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Detta kan vanligtvis lösas genom att ge webbservern skrivåtkomst till mappen för appar eller genom att inaktivera appbutiken i konfigurationsfilen. Se %s" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ta_LK.js b/docker/overlays/nextcloud/html/lib/l10n/ta_LK.js new file mode 100644 index 0000000..d5006d7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ta_LK.js @@ -0,0 +1,58 @@ +OC.L10N.register( + "lib", + { + "today" : "இன்று", + "yesterday" : "நேற்று", + "last month" : "கடந்த மாதம்", + "last year" : "கடந்த வருடம்", + "seconds ago" : "செக்கன்களுக்கு முன்", + "__language_name__" : "தமிழ்", + "Help" : "உதவி", + "Apps" : "செயலிகள்", + "Settings" : "அமைப்புகள்", + "Log out" : "விடுபதிகை செய்க", + "Users" : "பயனாளர்", + "Could not find category \"%s\"" : "பிரிவு \"%s\" ஐ கண்டுப்பிடிக்க முடியவில்லை", + "Sunday" : "ஞாயிற்றுக்கிழமை", + "Monday" : "திங்கட்கிழமை", + "Tuesday" : "செவ்வாய்க்கிழமை", + "Wednesday" : "புதன்கிழமை", + "Thursday" : "வியாழக்கிழமை", + "Friday" : "வெள்ளிக்கிழமை", + "Saturday" : "சனிக்கிழமை", + "Sun." : "ஞாயிறு", + "Mon." : "திங்கள்", + "Tue." : "செவ்வாய்", + "Wed." : "புதன்", + "Thu." : "வியாழன்", + "Fri." : "வெள்ளி", + "Sat." : "சனி", + "January" : "தை", + "February" : "மாசி", + "March" : "பங்குனி", + "April" : "சித்திரை", + "May" : "வைகாசி", + "June" : "ஆனி", + "July" : "ஆடி", + "August" : "ஆவணி", + "September" : "புரட்டாசி", + "October" : "ஐப்பசி", + "November" : "கார்த்திகை", + "December" : "மார்கழி", + "Jan." : "தை", + "Feb." : "மாசி", + "Mar." : "பங்குனி", + "Apr." : "சித்திரை", + "May." : "வைகாசி", + "Jun." : "ஆனி", + "Jul." : "ஆடி", + "Aug." : "ஆவணி", + "Sep." : "புரட்டாதி", + "Oct." : "ஐப்பசி", + "Nov." : "கார்த்திகை", + "Dec." : "மார்கழி", + "Application is not enabled" : "செயலி இயலுமைப்படுத்தப்படவில்லை", + "Authentication error" : "அத்தாட்சிப்படுத்தலில் வழு", + "Token expired. Please reload page." : "அடையாளவில்லை காலாவதியாகிவிட்டது. தயவுசெய்து பக்கத்தை மீள் ஏற்றுக." +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ta_LK.json b/docker/overlays/nextcloud/html/lib/l10n/ta_LK.json new file mode 100644 index 0000000..eaf2b0c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ta_LK.json @@ -0,0 +1,56 @@ +{ "translations": { + "today" : "இன்று", + "yesterday" : "நேற்று", + "last month" : "கடந்த மாதம்", + "last year" : "கடந்த வருடம்", + "seconds ago" : "செக்கன்களுக்கு முன்", + "__language_name__" : "தமிழ்", + "Help" : "உதவி", + "Apps" : "செயலிகள்", + "Settings" : "அமைப்புகள்", + "Log out" : "விடுபதிகை செய்க", + "Users" : "பயனாளர்", + "Could not find category \"%s\"" : "பிரிவு \"%s\" ஐ கண்டுப்பிடிக்க முடியவில்லை", + "Sunday" : "ஞாயிற்றுக்கிழமை", + "Monday" : "திங்கட்கிழமை", + "Tuesday" : "செவ்வாய்க்கிழமை", + "Wednesday" : "புதன்கிழமை", + "Thursday" : "வியாழக்கிழமை", + "Friday" : "வெள்ளிக்கிழமை", + "Saturday" : "சனிக்கிழமை", + "Sun." : "ஞாயிறு", + "Mon." : "திங்கள்", + "Tue." : "செவ்வாய்", + "Wed." : "புதன்", + "Thu." : "வியாழன்", + "Fri." : "வெள்ளி", + "Sat." : "சனி", + "January" : "தை", + "February" : "மாசி", + "March" : "பங்குனி", + "April" : "சித்திரை", + "May" : "வைகாசி", + "June" : "ஆனி", + "July" : "ஆடி", + "August" : "ஆவணி", + "September" : "புரட்டாசி", + "October" : "ஐப்பசி", + "November" : "கார்த்திகை", + "December" : "மார்கழி", + "Jan." : "தை", + "Feb." : "மாசி", + "Mar." : "பங்குனி", + "Apr." : "சித்திரை", + "May." : "வைகாசி", + "Jun." : "ஆனி", + "Jul." : "ஆடி", + "Aug." : "ஆவணி", + "Sep." : "புரட்டாதி", + "Oct." : "ஐப்பசி", + "Nov." : "கார்த்திகை", + "Dec." : "மார்கழி", + "Application is not enabled" : "செயலி இயலுமைப்படுத்தப்படவில்லை", + "Authentication error" : "அத்தாட்சிப்படுத்தலில் வழு", + "Token expired. Please reload page." : "அடையாளவில்லை காலாவதியாகிவிட்டது. தயவுசெய்து பக்கத்தை மீள் ஏற்றுக." +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/th.js b/docker/overlays/nextcloud/html/lib/l10n/th.js new file mode 100644 index 0000000..a21b9cd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/th.js @@ -0,0 +1,144 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "ไม่สามารถเขียนลงในไดเรกทอรี \"การตั้งค่า\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "นี้จะสามารถแก้ไขได้โดยให้สิทธิ์การเขียนของเว็บเซิร์ฟเวอร์ไปยังการตั้งค่าไดเรกทอรี", + "See %s" : "เห็น %s", + "Sample configuration detected" : "ตรวจพบการกำหนดค่าตัวอย่าง", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "ตรวจพบว่าการกำหนดค่าตัวอย่างที่ถูกคัดลอก นี้สามารถทำลายการติดตั้งของคุณและไม่ได้รับการสนับสนุน โปรดอ่านเอกสารก่อนที่จะดำเนินการเปลี่ยนแปลงใน config.php", + "PHP %s or higher is required." : "จำเป็นต้องมี PHP รุ่น %s หรือที่สูงกว่า ", + "PHP with a version lower than %s is required." : "รุ่น PHP ของคุณต่ำกว่า %s", + "%sbit or higher PHP required." : "%sบิต หรือ PHP จะต้องเป็นรุ่นสูงกว่าที่กำหนด", + "The command line tool %s could not be found" : "ไม่พบเครื่องมือบรรทัดคำสั่ง %s ", + "The library %s is not available." : "ไลบรารี %s ไม่สามารถใช้ได้", + "Authentication" : "รับรองความถูกต้อง", + "Unknown filetype" : "ไม่รู้จักชนิดของไฟล์", + "Invalid image" : "รูปภาพไม่ถูกต้อง", + "today" : "วันนี้", + "yesterday" : "เมื่อวานนี้", + "_%n day ago_::_%n days ago_" : ["%n วันที่ผ่านมา"], + "last month" : "เดือนที่แล้ว", + "_%n month ago_::_%n months ago_" : ["%n เดือนที่ผ่านมา"], + "last year" : "ปีที่แล้ว", + "_%n year ago_::_%n years ago_" : ["%n ปีที่ผ่านมา"], + "_%n hour ago_::_%n hours ago_" : ["%n ชั่วโมงที่ผ่านมา"], + "_%n minute ago_::_%n minutes ago_" : ["%n นาทีที่ผ่านมา"], + "seconds ago" : "วินาที ก่อนหน้านี้", + "File name is a reserved word" : "ชื่อแฟ้มเป็นคำสงวน", + "File name contains at least one invalid character" : "ชื่อแฟ้มมีหนึ่งตัวอักษรที่ไม่ถูกต้อง", + "File name is too long" : "ชื่อแฟ้มยาวเกินไป", + "Dot files are not allowed" : "ชื่อไฟล์ห้ามมีจุด", + "Empty filename is not allowed" : "ชื่อไฟล์ห้ามว่างเปล่า", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "แอพฯ \"%s\" ไม่สามารถติดตั้งได้เพราะไฟล์ appInfo ไม่สามารถอ่านได้", + "__language_name__" : "ภาษาไทย - Thai languages", + "Help" : "ช่วยเหลือ", + "Apps" : "แอปฯ", + "Settings" : "ตั้งค่า", + "Log out" : "ออกจากระบบ", + "Users" : "ผู้ใช้งาน", + "Unknown user" : "ไม่รู้จักผู้ใช้", + "%s enter the database username and name." : "%s ป้อนชื่อผู้ใช้ฐานข้อมูล และชื่อ", + "%s enter the database username." : "%s ใส่ชื่อผู้ใช้ฐานข้อมูล", + "%s enter the database name." : "%s ใส่ชื่อฐานข้อมูล", + "%s you may not use dots in the database name" : "%s บางที่คุณไม่ควรมีจุดในชื่อฐานข้อมูล", + "Oracle connection could not be established" : "ไม่สามารถสร้างการเชื่อมต่อกับ Oracle ", + "Oracle username and/or password not valid" : "Oracle ชื่อผู้ใช้ และ/หรือ รหัสผ่านไม่ถูกต้อง", + "PostgreSQL username and/or password not valid" : "PostgreSQL ชื่อผู้ใช้ และ/หรือ รหัสผ่านไม่ถูกต้อง", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "ระบบปฏิบัติการ Mac OS X ไม่ได้รับการสนับสนุนและ %s จะไม่ทำงานบนแพลตฟอร์มนี้ ใช้มันบนความเสี่ยงของคุณเอง!", + "For the best results, please consider using a GNU/Linux server instead." : "เพื่อให้ได้ผลลัพธ์ที่ดีที่สุดโปรดพิจารณาใช้เซิร์ฟเวอร์ GNU/Linux แทน", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "ดูเหมือนว่า %s ทำงานบน PHP 32 บิต และ open_basedir ได้ถูกกำหนดค่าใน php.ini ซึ่งจะมีปัญหาหากไฟล์มีขนาดกว่า 4 GB กิกะไบต์", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "กรุณาลบการตั้งค่า open_basedir ภายใน php.ini ของหรือเปลี่ยนไปใช้ PHP รุ่น 64 บิตแทน", + "Set an admin username." : "ตั้งค่าชื่อผู้ดูแลระบบ", + "Set an admin password." : "ตั้งค่ารหัสผ่านผู้ดูแลระบบ", + "Can't create or write into the data directory %s" : "ไม่สามารถสร้างหรือเขียนลงในข้อมูลไดเรกทอรี %s", + "Invalid Federated Cloud ID" : "ไอดีคลาวด์ในเครือไม่ถูกต้อง", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "การแชร์แบ็กเอนด์ %s ต้องใช้อินเตอร์เฟซ OCP\\Share_Backend", + "Sharing backend %s not found" : "ไม่พบการแชร์แบ็กเอนด์ %s", + "Sharing backend for %s not found" : "ไม่พบการแชร์แบ็กเอนด์สำหรับ %s", + "You are not allowed to share %s" : "คุณยังไม่ได้รับอนุญาตให้แชร์ %s", + "Expiration date is in the past" : "วันหมดอายุอยู่ในอดีตที่ผ่านมา", + "Could not find category \"%s\"" : "ไม่พบหมวดหมู่ \"%s\"", + "Sunday" : "วันอาทิตย์", + "Monday" : "วันจันทร์", + "Tuesday" : "วันอังคาร", + "Wednesday" : "วันพุธ", + "Thursday" : "วันพฤหัสบดี", + "Friday" : "วันศุกร์", + "Saturday" : "วันเสาร์", + "Sun." : "อา.", + "Mon." : "จ.", + "Tue." : "อ.", + "Wed." : "พ.", + "Thu." : "พฤ.", + "Fri." : "ศ.", + "Sat." : "ส.", + "Su" : "อา", + "Mo" : "จัน", + "Tu" : "อัง", + "We" : "พุธ", + "Th" : "พฤ", + "Fr" : "ศุก", + "Sa" : "เสา", + "January" : "มกราคม", + "February" : "กุมภาพันธ์", + "March" : "มีนาคม", + "April" : "เมษายน", + "May" : "พฤษภาคม", + "June" : "มิถุนายน", + "July" : "กรกฏาคม", + "August" : "สิงหาคม", + "September" : "กันยายน", + "October" : "ตุลาคม", + "November" : "พฤศจิกายน", + "December" : "ธันวาคม", + "Jan." : "ม.ค.", + "Feb." : "ก.พ.", + "Mar." : "มี.ค.", + "Apr." : "เม.ย.", + "May." : "พ.ค.", + "Jun." : "มิ.ย.", + "Jul." : "ก.ค.", + "Aug." : "ส.ค.", + "Sep." : "ก.ย.", + "Oct." : "ต.ค.", + "Nov." : "พ.ย.", + "Dec." : "ธ.ค.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ชื่อผู้ใช้จะใช้ได้แค่อักษรดังต่อไปนี้: \"a-z\", \"A-Z\", \"0-9\" และ \"_.@-'\"", + "A valid username must be provided" : "จะต้องระบุชื่อผู้ใช้ที่ถูกต้อง", + "A valid password must be provided" : "รหัสผ่านที่ถูกต้องจะต้องให้", + "The username is already being used" : "มีคนใช้ชื่อผู้ใช้นี้ไปแล้ว", + "File is currently busy, please try again later" : "ขณะนี้ไฟล์กำลังใช้งานอยู่ โปรดลองอีกครั้งในภายหลัง", + "Can't read file" : "ไม่สามารถอ่านไฟล์", + "Application is not enabled" : "แอพพลิเคชั่นดังกล่าวยังไม่ได้เปิดใช้งาน", + "Authentication error" : "เกิดข้อผิดพลาดในสิทธิ์การเข้าใช้งาน", + "Token expired. Please reload page." : "รหัสยืนยันความถูกต้องหมดอายุแล้ว กรุณาโหลดหน้าเว็บใหม่อีกครั้ง", + "No database drivers (sqlite, mysql, or postgresql) installed." : "ไม่มีไดรเวอร์ฐานข้อมูล (sqlite, mysql, or postgresql) ที่ถูกติดตั้ง", + "Cannot write into \"config\" directory" : "ไม่สามารถเขียนลงในไดเรกทอรี \"การตั้งค่า\"", + "Cannot write into \"apps\" directory" : "ไม่สามารถเขียนลงในไดเรกทอรี \"แอพฯ\"", + "Setting locale to %s failed" : "ตั้งค่าต้นทาง %s ล้มเหลว", + "Please install one of these locales on your system and restart your webserver." : "กรุณาติดตั้งหนึ่งในต้นทางเหล่านี้บนระบบของคุณและเริ่มต้นเว็บเซิร์ฟเวอร์ของคุณ", + "PHP module %s not installed." : "โมดูล PHP %s ไม่ได้ถูกติดตั้ง", + "Please ask your server administrator to install the module." : "โปรดสอบถามผู้ดูแลระบบเซิร์ฟเวอร์ของคุณเพื่อติดตั้งโมดูล", + "PHP setting \"%s\" is not set to \"%s\"." : "การตั้งค่า PHP \"%s\" ไม่ได้ตั้งค่าเป็น \"%s\"", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ถูกตั้งเป็น \"%s\" แทนที่จะเป็น \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "หากต้องการแก้ไขปัญหานี้กรุณาแก้ mbstring.func_overload เป็น 0 ในไฟล์ php.ini ของคุณ", + "To fix this issue update your libxml2 version and restart your web server." : "เพื่อแก้ไขปัญหานี้ กรุณาอัพเดทรุ่นของ libxml2 และรีสตาร์ทเว็บเซิร์ฟเวอร์ของคุณ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "เห็นได้ชัดว่า PHP มีการตั้งค่าเพื่อดึงบล็อกเอกสารแบบอินไลน์ ซึ่งจะทำให้แอพพลิเคชันไม่สามารถเข้าถึงได้", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "นี้อาจเกิดจาก cache/accelerator อย่างเช่น Zend OPcache หรือ eAccelerator", + "PHP modules have been installed, but they are still listed as missing?" : "โมดูล PHP ได้รับการติดตั้ง แต่พวกเขาไม่ได้ระบุไว้หรือมันอาจหายไป?", + "Please ask your server administrator to restart the web server." : "โปรดสอบถามผู้ดูแลระบบเซิร์ฟเวอร์ของคุณเพื่อเริ่มการทำงานของเว็บเซิร์ฟเวอร์", + "PostgreSQL >= 9 required" : "จำเป็นต้องใช้ PostgreSQL รุ่น >= 9", + "Please upgrade your database version" : "กรุณาอัพเดทฐานข้อมูลของคุณ", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "กรุณาเปลี่ยนสิทธิ์การเข้าถึงเป็น 0770 เพื่อให้ไดเรกทอรีไม่สามารถแก้ไขโดยผู้ใช้อื่น", + "Check the value of \"datadirectory\" in your configuration" : "ตรวจสอบค่าของ \"datadirectory\" ในการกำหนดค่าของคุณ", + "Could not obtain lock type %d on \"%s\"." : "ไม่สามารถรับล็อคชนิด %d บน \"%s\"", + "Storage unauthorized. %s" : "การจัดเก็บข้อมูลไม่ได้รับอนุญาต %s", + "Storage incomplete configuration. %s" : "การตั้งค่าการจัดเก็บข้อมูลไม่สำเร็จ %s", + "Storage connection error. %s" : "ข้อผิดพลาดการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s", + "Storage connection timeout. %s" : "หมดเวลาการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s", + "Following databases are supported: %s" : "ฐานข้อมูลต่อไปนี้ได้รับการสนับสนุน: %s", + "Following platforms are supported: %s" : "แพลตฟอร์มต่อไปนี้ได้รับการสนับสนุน: %s", + "Sharing" : "แชร์ข้อมูล", + "Personal info" : "ข้อมูลส่วนบุคคล" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/th.json b/docker/overlays/nextcloud/html/lib/l10n/th.json new file mode 100644 index 0000000..3ff7e86 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/th.json @@ -0,0 +1,142 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "ไม่สามารถเขียนลงในไดเรกทอรี \"การตั้งค่า\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "นี้จะสามารถแก้ไขได้โดยให้สิทธิ์การเขียนของเว็บเซิร์ฟเวอร์ไปยังการตั้งค่าไดเรกทอรี", + "See %s" : "เห็น %s", + "Sample configuration detected" : "ตรวจพบการกำหนดค่าตัวอย่าง", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "ตรวจพบว่าการกำหนดค่าตัวอย่างที่ถูกคัดลอก นี้สามารถทำลายการติดตั้งของคุณและไม่ได้รับการสนับสนุน โปรดอ่านเอกสารก่อนที่จะดำเนินการเปลี่ยนแปลงใน config.php", + "PHP %s or higher is required." : "จำเป็นต้องมี PHP รุ่น %s หรือที่สูงกว่า ", + "PHP with a version lower than %s is required." : "รุ่น PHP ของคุณต่ำกว่า %s", + "%sbit or higher PHP required." : "%sบิต หรือ PHP จะต้องเป็นรุ่นสูงกว่าที่กำหนด", + "The command line tool %s could not be found" : "ไม่พบเครื่องมือบรรทัดคำสั่ง %s ", + "The library %s is not available." : "ไลบรารี %s ไม่สามารถใช้ได้", + "Authentication" : "รับรองความถูกต้อง", + "Unknown filetype" : "ไม่รู้จักชนิดของไฟล์", + "Invalid image" : "รูปภาพไม่ถูกต้อง", + "today" : "วันนี้", + "yesterday" : "เมื่อวานนี้", + "_%n day ago_::_%n days ago_" : ["%n วันที่ผ่านมา"], + "last month" : "เดือนที่แล้ว", + "_%n month ago_::_%n months ago_" : ["%n เดือนที่ผ่านมา"], + "last year" : "ปีที่แล้ว", + "_%n year ago_::_%n years ago_" : ["%n ปีที่ผ่านมา"], + "_%n hour ago_::_%n hours ago_" : ["%n ชั่วโมงที่ผ่านมา"], + "_%n minute ago_::_%n minutes ago_" : ["%n นาทีที่ผ่านมา"], + "seconds ago" : "วินาที ก่อนหน้านี้", + "File name is a reserved word" : "ชื่อแฟ้มเป็นคำสงวน", + "File name contains at least one invalid character" : "ชื่อแฟ้มมีหนึ่งตัวอักษรที่ไม่ถูกต้อง", + "File name is too long" : "ชื่อแฟ้มยาวเกินไป", + "Dot files are not allowed" : "ชื่อไฟล์ห้ามมีจุด", + "Empty filename is not allowed" : "ชื่อไฟล์ห้ามว่างเปล่า", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "แอพฯ \"%s\" ไม่สามารถติดตั้งได้เพราะไฟล์ appInfo ไม่สามารถอ่านได้", + "__language_name__" : "ภาษาไทย - Thai languages", + "Help" : "ช่วยเหลือ", + "Apps" : "แอปฯ", + "Settings" : "ตั้งค่า", + "Log out" : "ออกจากระบบ", + "Users" : "ผู้ใช้งาน", + "Unknown user" : "ไม่รู้จักผู้ใช้", + "%s enter the database username and name." : "%s ป้อนชื่อผู้ใช้ฐานข้อมูล และชื่อ", + "%s enter the database username." : "%s ใส่ชื่อผู้ใช้ฐานข้อมูล", + "%s enter the database name." : "%s ใส่ชื่อฐานข้อมูล", + "%s you may not use dots in the database name" : "%s บางที่คุณไม่ควรมีจุดในชื่อฐานข้อมูล", + "Oracle connection could not be established" : "ไม่สามารถสร้างการเชื่อมต่อกับ Oracle ", + "Oracle username and/or password not valid" : "Oracle ชื่อผู้ใช้ และ/หรือ รหัสผ่านไม่ถูกต้อง", + "PostgreSQL username and/or password not valid" : "PostgreSQL ชื่อผู้ใช้ และ/หรือ รหัสผ่านไม่ถูกต้อง", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "ระบบปฏิบัติการ Mac OS X ไม่ได้รับการสนับสนุนและ %s จะไม่ทำงานบนแพลตฟอร์มนี้ ใช้มันบนความเสี่ยงของคุณเอง!", + "For the best results, please consider using a GNU/Linux server instead." : "เพื่อให้ได้ผลลัพธ์ที่ดีที่สุดโปรดพิจารณาใช้เซิร์ฟเวอร์ GNU/Linux แทน", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "ดูเหมือนว่า %s ทำงานบน PHP 32 บิต และ open_basedir ได้ถูกกำหนดค่าใน php.ini ซึ่งจะมีปัญหาหากไฟล์มีขนาดกว่า 4 GB กิกะไบต์", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "กรุณาลบการตั้งค่า open_basedir ภายใน php.ini ของหรือเปลี่ยนไปใช้ PHP รุ่น 64 บิตแทน", + "Set an admin username." : "ตั้งค่าชื่อผู้ดูแลระบบ", + "Set an admin password." : "ตั้งค่ารหัสผ่านผู้ดูแลระบบ", + "Can't create or write into the data directory %s" : "ไม่สามารถสร้างหรือเขียนลงในข้อมูลไดเรกทอรี %s", + "Invalid Federated Cloud ID" : "ไอดีคลาวด์ในเครือไม่ถูกต้อง", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "การแชร์แบ็กเอนด์ %s ต้องใช้อินเตอร์เฟซ OCP\\Share_Backend", + "Sharing backend %s not found" : "ไม่พบการแชร์แบ็กเอนด์ %s", + "Sharing backend for %s not found" : "ไม่พบการแชร์แบ็กเอนด์สำหรับ %s", + "You are not allowed to share %s" : "คุณยังไม่ได้รับอนุญาตให้แชร์ %s", + "Expiration date is in the past" : "วันหมดอายุอยู่ในอดีตที่ผ่านมา", + "Could not find category \"%s\"" : "ไม่พบหมวดหมู่ \"%s\"", + "Sunday" : "วันอาทิตย์", + "Monday" : "วันจันทร์", + "Tuesday" : "วันอังคาร", + "Wednesday" : "วันพุธ", + "Thursday" : "วันพฤหัสบดี", + "Friday" : "วันศุกร์", + "Saturday" : "วันเสาร์", + "Sun." : "อา.", + "Mon." : "จ.", + "Tue." : "อ.", + "Wed." : "พ.", + "Thu." : "พฤ.", + "Fri." : "ศ.", + "Sat." : "ส.", + "Su" : "อา", + "Mo" : "จัน", + "Tu" : "อัง", + "We" : "พุธ", + "Th" : "พฤ", + "Fr" : "ศุก", + "Sa" : "เสา", + "January" : "มกราคม", + "February" : "กุมภาพันธ์", + "March" : "มีนาคม", + "April" : "เมษายน", + "May" : "พฤษภาคม", + "June" : "มิถุนายน", + "July" : "กรกฏาคม", + "August" : "สิงหาคม", + "September" : "กันยายน", + "October" : "ตุลาคม", + "November" : "พฤศจิกายน", + "December" : "ธันวาคม", + "Jan." : "ม.ค.", + "Feb." : "ก.พ.", + "Mar." : "มี.ค.", + "Apr." : "เม.ย.", + "May." : "พ.ค.", + "Jun." : "มิ.ย.", + "Jul." : "ก.ค.", + "Aug." : "ส.ค.", + "Sep." : "ก.ย.", + "Oct." : "ต.ค.", + "Nov." : "พ.ย.", + "Dec." : "ธ.ค.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ชื่อผู้ใช้จะใช้ได้แค่อักษรดังต่อไปนี้: \"a-z\", \"A-Z\", \"0-9\" และ \"_.@-'\"", + "A valid username must be provided" : "จะต้องระบุชื่อผู้ใช้ที่ถูกต้อง", + "A valid password must be provided" : "รหัสผ่านที่ถูกต้องจะต้องให้", + "The username is already being used" : "มีคนใช้ชื่อผู้ใช้นี้ไปแล้ว", + "File is currently busy, please try again later" : "ขณะนี้ไฟล์กำลังใช้งานอยู่ โปรดลองอีกครั้งในภายหลัง", + "Can't read file" : "ไม่สามารถอ่านไฟล์", + "Application is not enabled" : "แอพพลิเคชั่นดังกล่าวยังไม่ได้เปิดใช้งาน", + "Authentication error" : "เกิดข้อผิดพลาดในสิทธิ์การเข้าใช้งาน", + "Token expired. Please reload page." : "รหัสยืนยันความถูกต้องหมดอายุแล้ว กรุณาโหลดหน้าเว็บใหม่อีกครั้ง", + "No database drivers (sqlite, mysql, or postgresql) installed." : "ไม่มีไดรเวอร์ฐานข้อมูล (sqlite, mysql, or postgresql) ที่ถูกติดตั้ง", + "Cannot write into \"config\" directory" : "ไม่สามารถเขียนลงในไดเรกทอรี \"การตั้งค่า\"", + "Cannot write into \"apps\" directory" : "ไม่สามารถเขียนลงในไดเรกทอรี \"แอพฯ\"", + "Setting locale to %s failed" : "ตั้งค่าต้นทาง %s ล้มเหลว", + "Please install one of these locales on your system and restart your webserver." : "กรุณาติดตั้งหนึ่งในต้นทางเหล่านี้บนระบบของคุณและเริ่มต้นเว็บเซิร์ฟเวอร์ของคุณ", + "PHP module %s not installed." : "โมดูล PHP %s ไม่ได้ถูกติดตั้ง", + "Please ask your server administrator to install the module." : "โปรดสอบถามผู้ดูแลระบบเซิร์ฟเวอร์ของคุณเพื่อติดตั้งโมดูล", + "PHP setting \"%s\" is not set to \"%s\"." : "การตั้งค่า PHP \"%s\" ไม่ได้ตั้งค่าเป็น \"%s\"", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload ถูกตั้งเป็น \"%s\" แทนที่จะเป็น \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "หากต้องการแก้ไขปัญหานี้กรุณาแก้ mbstring.func_overload เป็น 0 ในไฟล์ php.ini ของคุณ", + "To fix this issue update your libxml2 version and restart your web server." : "เพื่อแก้ไขปัญหานี้ กรุณาอัพเดทรุ่นของ libxml2 และรีสตาร์ทเว็บเซิร์ฟเวอร์ของคุณ", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "เห็นได้ชัดว่า PHP มีการตั้งค่าเพื่อดึงบล็อกเอกสารแบบอินไลน์ ซึ่งจะทำให้แอพพลิเคชันไม่สามารถเข้าถึงได้", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "นี้อาจเกิดจาก cache/accelerator อย่างเช่น Zend OPcache หรือ eAccelerator", + "PHP modules have been installed, but they are still listed as missing?" : "โมดูล PHP ได้รับการติดตั้ง แต่พวกเขาไม่ได้ระบุไว้หรือมันอาจหายไป?", + "Please ask your server administrator to restart the web server." : "โปรดสอบถามผู้ดูแลระบบเซิร์ฟเวอร์ของคุณเพื่อเริ่มการทำงานของเว็บเซิร์ฟเวอร์", + "PostgreSQL >= 9 required" : "จำเป็นต้องใช้ PostgreSQL รุ่น >= 9", + "Please upgrade your database version" : "กรุณาอัพเดทฐานข้อมูลของคุณ", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "กรุณาเปลี่ยนสิทธิ์การเข้าถึงเป็น 0770 เพื่อให้ไดเรกทอรีไม่สามารถแก้ไขโดยผู้ใช้อื่น", + "Check the value of \"datadirectory\" in your configuration" : "ตรวจสอบค่าของ \"datadirectory\" ในการกำหนดค่าของคุณ", + "Could not obtain lock type %d on \"%s\"." : "ไม่สามารถรับล็อคชนิด %d บน \"%s\"", + "Storage unauthorized. %s" : "การจัดเก็บข้อมูลไม่ได้รับอนุญาต %s", + "Storage incomplete configuration. %s" : "การตั้งค่าการจัดเก็บข้อมูลไม่สำเร็จ %s", + "Storage connection error. %s" : "ข้อผิดพลาดการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s", + "Storage connection timeout. %s" : "หมดเวลาการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s", + "Following databases are supported: %s" : "ฐานข้อมูลต่อไปนี้ได้รับการสนับสนุน: %s", + "Following platforms are supported: %s" : "แพลตฟอร์มต่อไปนี้ได้รับการสนับสนุน: %s", + "Sharing" : "แชร์ข้อมูล", + "Personal info" : "ข้อมูลส่วนบุคคล" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/tk.js b/docker/overlays/nextcloud/html/lib/l10n/tk.js new file mode 100644 index 0000000..f086adb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/tk.js @@ -0,0 +1,6 @@ +OC.L10N.register( + "lib", + { + "Settings" : "Sazlamalar" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/tk.json b/docker/overlays/nextcloud/html/lib/l10n/tk.json new file mode 100644 index 0000000..5510280 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/tk.json @@ -0,0 +1,4 @@ +{ "translations": { + "Settings" : "Sazlamalar" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/tr.js b/docker/overlays/nextcloud/html/lib/l10n/tr.js new file mode 100644 index 0000000..d592547 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/tr.js @@ -0,0 +1,240 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"config\" klasörüne yazılamadı!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ya da config.php dosyasının salt okunur olarak kalmasını istiyorsanız içindeki \"config_is_read_only\" seçeneğini true olarak ayarlayın.", + "See %s" : "Şuraya bakın: %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ya da config.php dosyasının salt okunur olarak kalmasını istiyorsanız içindeki \"config_is_read_only\" seçeneğini true olarak ayarlayın. %s bölümüne bakabilirsiniz", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.", + "Sample configuration detected" : "Örnek yapılandırma algılandı", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun", + "Other activities" : "Diğer işlemler", + "%1$s and %2$s" : "%1$s ve %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ve %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ve %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ve %5$s", + "Education Edition" : "Eğitim Sürümü", + "Enterprise bundle" : "Kurumsal paket", + "Groupware bundle" : "Grup çalışması paketi", + "Hub bundle" : "Toplayıcı paketi", + "Social sharing bundle" : "Sosyal ağ paketi", + "PHP %s or higher is required." : "PHP %s ya da daha sonraki bir sürümü gerekli.", + "PHP with a version lower than %s is required." : "PHP %s ya da daha önceki bir sürümü gerekli.", + "%sbit or higher PHP required." : "%sbit ya da daha sonraki bir PHP sürümü gerekli.", + "The following architectures are supported: %s" : "Şu mimariler destekleniyor: %s", + "The following databases are supported: %s" : "Şu veritabanları destekleniyor: %s", + "The command line tool %s could not be found" : "%s komut satırı aracı bulunamadı", + "The library %s is not available." : "%s kitaplığı bulunamadı.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s kitaplığının %2$s sonrası bir sürümü gerekli. Geçerli sürüm: %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s kitaplığının %2$s öncesi bir sürümü gerekli. Geçerli sürüm: %3$s.", + "The following platforms are supported: %s" : "Şu platformlar destekleniyor: %s", + "Server version %s or higher is required." : "Sunucu %s ya da daha sonraki bir sürüm olmalıdır.", + "Server version %s or lower is required." : "Sunucu %s ya da daha önceki bir sürüm olmalıdır.", + "Logged in user must be an admin or sub admin" : "Oturum açmış kullanıcı bir yönetici ya da alt yönetici olmalıdır", + "Logged in user must be an admin" : "Oturum açmış kullanıcı bir yönetici olmalıdır", + "Wiping of device %s has started" : "%s aygıtının silinmesine başlandı", + "Wiping of device »%s« has started" : "»%s« aygıtının silinmesine başlandı", + "»%s« started remote wipe" : "»%s« uzaktan silme işlemi başladı", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "»%s« aygıtı ya da uygulamasını uzaktan silme işlemine başlandı. İşlem tamamlandığında başka bir e-posta bildirimi alacaksınız", + "Wiping of device %s has finished" : "%s aygıtının silinmesine başlandı", + "Wiping of device »%s« has finished" : "»%s« aygıtının silinmesi tamamlandı", + "»%s« finished remote wipe" : "»%s« uzaktan silme işlemi tamamlandı", + "Device or application »%s« has finished the remote wipe process." : "»%s« aygıtı ya da uygulamasını uzaktan silme işlemi tamamlandı.", + "Remote wipe started" : "Uzaktan silme başlatıldı", + "A remote wipe was started on device %s" : "%s aygıtı üzerinde bir uzaktan silme işlemi başlatıldı", + "Remote wipe finished" : "Uzaktan silme tamamlandı", + "The remote wipe on %s has finished" : "%s üzerindeki uzaktan silme işlemi tamamlandı", + "Authentication" : "Kimlik Doğrulama", + "Unknown filetype" : "Dosya türü bilinmiyor", + "Invalid image" : "Görsel geçersiz", + "Avatar image is not square" : "Avatar görseli kare değil", + "today" : "bugün", + "tomorrow" : "yarın", + "yesterday" : "dün", + "_in %n day_::_in %n days_" : ["%n gün içinde","%n gün içinde"], + "_%n day ago_::_%n days ago_" : ["%n gün önce","%n gün önce"], + "next month" : "gelecek ay", + "last month" : "geçen ay", + "_in %n month_::_in %n months_" : ["%n ay içinde","%n ay içinde"], + "_%n month ago_::_%n months ago_" : ["%n ay önce","%n ay önce"], + "next year" : "gelecek yıl", + "last year" : "geçen yıl", + "_in %n year_::_in %n years_" : ["%n yıl içinde","%n yıl içinde"], + "_%n year ago_::_%n years ago_" : ["%n yıl önce","%n yıl önce"], + "_in %n hour_::_in %n hours_" : ["%n saat içinde","%n saat içinde"], + "_%n hour ago_::_%n hours ago_" : ["%n saat önce","%n saat önce"], + "_in %n minute_::_in %n minutes_" : ["%n dakika içinde","%n dakika içinde"], + "_%n minute ago_::_%n minutes ago_" : ["%n dakika önce","%n dakika önce"], + "in a few seconds" : "bir kaç saniye içinde", + "seconds ago" : "saniyeler önce", + "Empty file" : "Dosya boş", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kodlu modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.", + "File name is a reserved word" : "Bu dosya adı sistem kullanıma ayrılmıştır", + "File name contains at least one invalid character" : "Dosya adında en az bir geçersiz karakter var", + "File name is too long" : "Dosya adı çok uzun", + "Dot files are not allowed" : "Nokta dosyalarına izin verilmiyor", + "Empty filename is not allowed" : "Boş dosya adına izin verilmiyor", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfo dosyası okunamadığından \"%s\" uygulaması kurulamaz.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" uygulaması sunucu sürümüyle uyumlu olmadığından kurulamaz.", + "__language_name__" : "Türkçe", + "This is an automatically sent email, please do not reply." : "Bu ileti otomatik olarak gönderildiğinden lütfen yanıtlamayın.", + "Help" : "Yardım", + "Apps" : "Uygulamalar", + "Settings" : "Ayarlar", + "Log out" : "Oturumu Kapat", + "Users" : "Kullanıcılar", + "Unknown user" : "Kullanıcı bilinmiyor", + "Additional settings" : "Ek ayarlar", + "%s enter the database username and name." : "%s veritabanı adını ve kullanıcı adını yazın.", + "%s enter the database username." : "%s veritabanı kullanıcı adını yazın.", + "%s enter the database name." : "%s veritabanı adını yazın.", + "%s you may not use dots in the database name" : "%s veritabanı adında nokta kullanamayabilirsiniz", + "MySQL username and/or password not valid" : "MySQL kullanıcı adı ya da parolası geçersiz", + "You need to enter details of an existing account." : "Var olan bir hesabın bilgilerini yazmalısınız.", + "Oracle connection could not be established" : "Oracle ile bağlantı kurulamadı", + "Oracle username and/or password not valid" : "Oracle kullanıcı adı ya da parolası geçersiz", + "PostgreSQL username and/or password not valid" : "PostgreSQL kullanıcı adı ya da parolası geçersiz", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X desteklenmiyor ve %s bu platformda düzgün çalışmayacak. Kullanmaktan doğacak riskler size aittir!", + "For the best results, please consider using a GNU/Linux server instead." : "En iyi sonucu almak için GNU/Linux sunucusu kullanın.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Bu %s kopyası 32 bit PHP ortamında çalıştırılıyor ve open_basedir seçeneği php.ini dosyasından ayarlanmış gibi görünüyor. Bu yapılandırma 4 GB boyutundan büyük dosyalarda sorun çıkarır ve kullanılması önerilmez.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Lütfen php.ini dosyasındaki open_basedir ayarını kaldırın ya da 64-bit PHP sürümüne geçin.", + "Set an admin username." : "Bir yönetici kullanıcı adı yazın.", + "Set an admin password." : "Bir yönetici parolası yazın.", + "Can't create or write into the data directory %s" : "%s veri klasörü oluşturulamadı ya da içine yazılamadı", + "Invalid Federated Cloud ID" : "Birleşik Bulut Kimliği Geçersiz", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Paylaşım arka ucu %s OCP\\Share_Backend arayüzünü desteklemeli", + "Sharing backend %s not found" : "%s paylaşım arka ucu bulunamadı", + "Sharing backend for %s not found" : "%s için paylaşım arka ucu bulunamadı", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s sizinle »%2$s« ögesini paylaştı ve eklemenizi istiyor:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s sizinle »%2$s« ögesini paylaştı ve eklemenizi istiyor", + "»%s« added a note to a file shared with you" : "»%s« sizinle paylaştığı bir dosyaya bir not ekledi", + "Open »%s«" : "»%s« Aç", + "%1$s via %2$s" : "%1$s, %2$s aracılığıyla", + "You are not allowed to share %s" : "%s ögesini paylaşma izniniz yok", + "Can’t increase permissions of %s" : "%s izinleri arttırılamadı", + "Files can’t be shared with delete permissions" : "Silme izni ile dosya paylaşılamaz", + "Files can’t be shared with create permissions" : "Ekleme izni ile dosya paylaşılamaz", + "Expiration date is in the past" : "Son kullanma tarihi geçmişte", + "Can’t set expiration date more than %s days in the future" : "Son kullanma tarihi %sgünden sonrası olarak ayarlanamaz", + "%1$s shared »%2$s« with you" : "%1$s, sizinle »%2$s« ögesini paylaştı", + "%1$s shared »%2$s« with you." : "%1$s, sizinle »%2$s« ögesini paylaştı.", + "Click the button below to open it." : "Açmak için aşağıdaki düğmeye tıklayın.", + "The requested share does not exist anymore" : "Erişilmek istenilen paylaşım artık yok", + "Could not find category \"%s\"" : "\"%s\" kategorisi bulunamadı", + "Sunday" : "Pazar", + "Monday" : "Pazartesi", + "Tuesday" : "Salı", + "Wednesday" : "Çarşamba", + "Thursday" : "Perşembe", + "Friday" : "Cuma", + "Saturday" : "Cumartesi", + "Sun." : "Paz", + "Mon." : "Pzt", + "Tue." : "Sal", + "Wed." : "Çar", + "Thu." : "Per", + "Fri." : "Cum", + "Sat." : "Cmt", + "Su" : "Pa", + "Mo" : "Pt", + "Tu" : "Sa", + "We" : "Ça", + "Th" : "Pe", + "Fr" : "Cu", + "Sa" : "Ct", + "January" : "Ocak", + "February" : "Şubat", + "March" : "Mart", + "April" : "Nisan", + "May" : "Mayıs", + "June" : "Haziran", + "July" : "Temmuz", + "August" : "Ağustos", + "September" : "Eylül", + "October" : "Ekim", + "November" : "Kası", + "December" : "Aralı", + "Jan." : "Oca", + "Feb." : "Şub", + "Mar." : "Mar", + "Apr." : "Nis", + "May." : "May", + "Jun." : "Haz", + "Jul." : "Tem", + "Aug." : "Ağu", + "Sep." : "Eyl", + "Oct." : "Eki", + "Nov." : "Kas", + "Dec." : "Ara", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kullanıcı adında yalnız şu karakterler kullanılabilir: \"a-z\", \"A-Z\", \"0-9\", ve \"_.@-'\"", + "A valid username must be provided" : "Geçerli bir kullanıcı adı yazmalısınız", + "Username contains whitespace at the beginning or at the end" : "Kullanıcı adının başı ya da sonunda boşluk var", + "Username must not consist of dots only" : "Kullanıcı adı yalnız noktalardan oluşamaz", + "Username is invalid because files already exist for this user" : "Kullanıcı adı geçersiz, bu kullanıcı için zaten bazı dosyalar var", + "A valid password must be provided" : "Geçerli bir parola yazmalısınız", + "The username is already being used" : "Bu kullanıcı adı zaten var", + "Could not create user" : "Kullanıcı oluşturulamadı", + "User disabled" : "Kullanıcı devre dışı", + "Login canceled by app" : "Oturum açma uygulama tarafından iptal edildi", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "\"%1$s\" uygulaması, şu gereklilikler sağlanmadığı için kurulamıyor: %2$s", + "a safe home for all your data" : "verileriniz için güvenli bir barınak", + "File is currently busy, please try again later" : "Dosya şu anda meşgul, lütfen daha sonra deneyin", + "Can't read file" : "Dosya okunamadı", + "Application is not enabled" : "Uygulama etkinleştirilmemiş", + "Authentication error" : "Kimlik doğrulama sorunu", + "Token expired. Please reload page." : "Kodun süresi dolmuş. Lütfen sayfayı yenileyin.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Herhangi bir veritabanı sürücüsü (sqlite, mysql ya da postgresql) kurulmamış.", + "Cannot write into \"config\" directory" : "\"config\" klasörüne yazılamıyor", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir. %s bölümüne bakın", + "Cannot write into \"apps\" directory" : "\"apps\" klasörüne yazılamıyor", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Bu sorun genellikle, web sunucusuna apps klasörüne yazma izni verilerek ya da yapılandırma dosyasından uygulama mağazasını devre dışı bırakılarak çözülebilir.", + "Cannot create \"data\" directory" : "\"data\" klasörü oluşturulamadı", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Bu sorun genellikle, web sunucusuna kök klasöre yazma izni verilerek çözülebilir. %s bölümüne bakın", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "İzinler genellikle, web sunucusuna kök klasöre yazma izni verilerek düzeltilebilir. %s bölümüne bakın.", + "Setting locale to %s failed" : "Dil %s olarak ayarlanamadı", + "Please install one of these locales on your system and restart your webserver." : "Lütfen bu dillerden birini sisteminize kurun ve web sunucunuzu yeniden başlatın.", + "PHP module %s not installed." : "PHP %s modülü kurulmamış.", + "Please ask your server administrator to install the module." : "Lütfen modülü kurması için BT yöneticinizle görüşün.", + "PHP setting \"%s\" is not set to \"%s\"." : "\"%s\" PHP ayarı \"%s\" olarak ayarlanmamış.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini dosyasında bu ayar yapıldığında Nextcloud yeniden çalışır", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload, beklenen \"0\" değeri yerine \"%s\" olarak ayarlanmış", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bu sorunu çözmek için php.ini dosyasındaki mbstring.func_overload seçeneğini 0 olarak ayarlayın", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 sürümü en az 2.7.0 olmalıdır. Şu anda %s kurulu.", + "To fix this issue update your libxml2 version and restart your web server." : "Bu sorunu çözmek için libxml2 sürümünüzü güncelleyin ve web sunucusunu yeniden başlatın.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP girintili doc bloklarını ayıklamak üzere yapılandırılmış gibi görünüyor. Bu durum bazı çekirdek uygulamalarına erişilmesini engelleyecek.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Bu sorun genellikle Zend OPcache ya da eAccelerator gibi bir ön bellek/hızlandırıcı nedeniyle ortaya çıkar.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modülleri kurulmuş, ancak hala eksik olarak mı görünüyor?", + "Please ask your server administrator to restart the web server." : "Lütfen web sunucusunu yeniden başlatması için BT yöneticinizle görüşün.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 gerekli", + "Please upgrade your database version" : "Lütfen veritabanı sürümünüzü yükseltin", + "Your data directory is readable by other users" : "Veri klasörünüz diğer kullanıcılar tarafından okunabilir", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Lütfen izinleri 0770 olarak ayarlayarak diğer kullanıcıların klasörü görebilmesini sağlayın.", + "Your data directory must be an absolute path" : "Veri klasörünüz mutlak bir yol olmalıdır", + "Check the value of \"datadirectory\" in your configuration" : "Yapılandırmanızdaki \"datadirectory\" seçeneğini denetleyin", + "Your data directory is invalid" : "Veri klasörünüz geçersiz", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Veri klasörü kökünde \".ocdata\" adında bir dosya bulunduğundan emin olun.", + "Action \"%s\" not supported or implemented." : "\"%s\" işlemi desteklenmiyor ya da henüz kullanılamıyor.", + "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kodu hatalı", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "İsteğin tamamlanması için gerekli parametreler eksik: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kodu zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kodlu birleşik bulut hizmeti sağlayıcısı bulunamadı.", + "Could not obtain lock type %d on \"%s\"." : "\"%s\" için %d kilit türü alınamadı.", + "Storage unauthorized. %s" : "Depolamaya erişim izni yok. %s", + "Storage incomplete configuration. %s" : "Depolama yapılandırması tamamlanmamış. %s", + "Storage connection error. %s" : "Depolama bağlantısı sorunu. %s", + "Storage is temporarily not available" : "Depolama geçici olarak kullanılamıyor", + "Storage connection timeout. %s" : "Depolama bağlantısı zaman aşımı. %s", + "Following databases are supported: %s" : "Şu veritabanları destekleniyor: %s", + "Following platforms are supported: %s" : "Şu platformlar destekleniyor: %s", + "Overview" : "Özet", + "Basic settings" : "Temel Ayarlar", + "Sharing" : "Paylaşım", + "Security" : "Güvenlik", + "Groupware" : "Grup çalışması", + "Personal info" : "Kişisel Bilgiler", + "Mobile & desktop" : "Mobil ve masaüstü", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Bu sorun genellikle, web sunucusuna apps klasörüne yazma izni verilerek ya da yapılandırma dosyasından uygulama mağazası devre dışı bırakılarak çözülebilir. %s bölümüne bakın" +}, +"nplurals=2; plural=(n > 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/tr.json b/docker/overlays/nextcloud/html/lib/l10n/tr.json new file mode 100644 index 0000000..b4ae4da --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/tr.json @@ -0,0 +1,238 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"config\" klasörüne yazılamadı!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ya da config.php dosyasının salt okunur olarak kalmasını istiyorsanız içindeki \"config_is_read_only\" seçeneğini true olarak ayarlayın.", + "See %s" : "Şuraya bakın: %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir.", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ya da config.php dosyasının salt okunur olarak kalmasını istiyorsanız içindeki \"config_is_read_only\" seçeneğini true olarak ayarlayın. %s bölümüne bakabilirsiniz", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.", + "Sample configuration detected" : "Örnek yapılandırma algılandı", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun", + "Other activities" : "Diğer işlemler", + "%1$s and %2$s" : "%1$s ve %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s ve %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s ve %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s ve %5$s", + "Education Edition" : "Eğitim Sürümü", + "Enterprise bundle" : "Kurumsal paket", + "Groupware bundle" : "Grup çalışması paketi", + "Hub bundle" : "Toplayıcı paketi", + "Social sharing bundle" : "Sosyal ağ paketi", + "PHP %s or higher is required." : "PHP %s ya da daha sonraki bir sürümü gerekli.", + "PHP with a version lower than %s is required." : "PHP %s ya da daha önceki bir sürümü gerekli.", + "%sbit or higher PHP required." : "%sbit ya da daha sonraki bir PHP sürümü gerekli.", + "The following architectures are supported: %s" : "Şu mimariler destekleniyor: %s", + "The following databases are supported: %s" : "Şu veritabanları destekleniyor: %s", + "The command line tool %s could not be found" : "%s komut satırı aracı bulunamadı", + "The library %s is not available." : "%s kitaplığı bulunamadı.", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s kitaplığının %2$s sonrası bir sürümü gerekli. Geçerli sürüm: %3$s.", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s kitaplığının %2$s öncesi bir sürümü gerekli. Geçerli sürüm: %3$s.", + "The following platforms are supported: %s" : "Şu platformlar destekleniyor: %s", + "Server version %s or higher is required." : "Sunucu %s ya da daha sonraki bir sürüm olmalıdır.", + "Server version %s or lower is required." : "Sunucu %s ya da daha önceki bir sürüm olmalıdır.", + "Logged in user must be an admin or sub admin" : "Oturum açmış kullanıcı bir yönetici ya da alt yönetici olmalıdır", + "Logged in user must be an admin" : "Oturum açmış kullanıcı bir yönetici olmalıdır", + "Wiping of device %s has started" : "%s aygıtının silinmesine başlandı", + "Wiping of device »%s« has started" : "»%s« aygıtının silinmesine başlandı", + "»%s« started remote wipe" : "»%s« uzaktan silme işlemi başladı", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "»%s« aygıtı ya da uygulamasını uzaktan silme işlemine başlandı. İşlem tamamlandığında başka bir e-posta bildirimi alacaksınız", + "Wiping of device %s has finished" : "%s aygıtının silinmesine başlandı", + "Wiping of device »%s« has finished" : "»%s« aygıtının silinmesi tamamlandı", + "»%s« finished remote wipe" : "»%s« uzaktan silme işlemi tamamlandı", + "Device or application »%s« has finished the remote wipe process." : "»%s« aygıtı ya da uygulamasını uzaktan silme işlemi tamamlandı.", + "Remote wipe started" : "Uzaktan silme başlatıldı", + "A remote wipe was started on device %s" : "%s aygıtı üzerinde bir uzaktan silme işlemi başlatıldı", + "Remote wipe finished" : "Uzaktan silme tamamlandı", + "The remote wipe on %s has finished" : "%s üzerindeki uzaktan silme işlemi tamamlandı", + "Authentication" : "Kimlik Doğrulama", + "Unknown filetype" : "Dosya türü bilinmiyor", + "Invalid image" : "Görsel geçersiz", + "Avatar image is not square" : "Avatar görseli kare değil", + "today" : "bugün", + "tomorrow" : "yarın", + "yesterday" : "dün", + "_in %n day_::_in %n days_" : ["%n gün içinde","%n gün içinde"], + "_%n day ago_::_%n days ago_" : ["%n gün önce","%n gün önce"], + "next month" : "gelecek ay", + "last month" : "geçen ay", + "_in %n month_::_in %n months_" : ["%n ay içinde","%n ay içinde"], + "_%n month ago_::_%n months ago_" : ["%n ay önce","%n ay önce"], + "next year" : "gelecek yıl", + "last year" : "geçen yıl", + "_in %n year_::_in %n years_" : ["%n yıl içinde","%n yıl içinde"], + "_%n year ago_::_%n years ago_" : ["%n yıl önce","%n yıl önce"], + "_in %n hour_::_in %n hours_" : ["%n saat içinde","%n saat içinde"], + "_%n hour ago_::_%n hours ago_" : ["%n saat önce","%n saat önce"], + "_in %n minute_::_in %n minutes_" : ["%n dakika içinde","%n dakika içinde"], + "_%n minute ago_::_%n minutes ago_" : ["%n dakika önce","%n dakika önce"], + "in a few seconds" : "bir kaç saniye içinde", + "seconds ago" : "saniyeler önce", + "Empty file" : "Dosya boş", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kodlu modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.", + "File name is a reserved word" : "Bu dosya adı sistem kullanıma ayrılmıştır", + "File name contains at least one invalid character" : "Dosya adında en az bir geçersiz karakter var", + "File name is too long" : "Dosya adı çok uzun", + "Dot files are not allowed" : "Nokta dosyalarına izin verilmiyor", + "Empty filename is not allowed" : "Boş dosya adına izin verilmiyor", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "appinfo dosyası okunamadığından \"%s\" uygulaması kurulamaz.", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "\"%s\" uygulaması sunucu sürümüyle uyumlu olmadığından kurulamaz.", + "__language_name__" : "Türkçe", + "This is an automatically sent email, please do not reply." : "Bu ileti otomatik olarak gönderildiğinden lütfen yanıtlamayın.", + "Help" : "Yardım", + "Apps" : "Uygulamalar", + "Settings" : "Ayarlar", + "Log out" : "Oturumu Kapat", + "Users" : "Kullanıcılar", + "Unknown user" : "Kullanıcı bilinmiyor", + "Additional settings" : "Ek ayarlar", + "%s enter the database username and name." : "%s veritabanı adını ve kullanıcı adını yazın.", + "%s enter the database username." : "%s veritabanı kullanıcı adını yazın.", + "%s enter the database name." : "%s veritabanı adını yazın.", + "%s you may not use dots in the database name" : "%s veritabanı adında nokta kullanamayabilirsiniz", + "MySQL username and/or password not valid" : "MySQL kullanıcı adı ya da parolası geçersiz", + "You need to enter details of an existing account." : "Var olan bir hesabın bilgilerini yazmalısınız.", + "Oracle connection could not be established" : "Oracle ile bağlantı kurulamadı", + "Oracle username and/or password not valid" : "Oracle kullanıcı adı ya da parolası geçersiz", + "PostgreSQL username and/or password not valid" : "PostgreSQL kullanıcı adı ya da parolası geçersiz", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X desteklenmiyor ve %s bu platformda düzgün çalışmayacak. Kullanmaktan doğacak riskler size aittir!", + "For the best results, please consider using a GNU/Linux server instead." : "En iyi sonucu almak için GNU/Linux sunucusu kullanın.", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Bu %s kopyası 32 bit PHP ortamında çalıştırılıyor ve open_basedir seçeneği php.ini dosyasından ayarlanmış gibi görünüyor. Bu yapılandırma 4 GB boyutundan büyük dosyalarda sorun çıkarır ve kullanılması önerilmez.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Lütfen php.ini dosyasındaki open_basedir ayarını kaldırın ya da 64-bit PHP sürümüne geçin.", + "Set an admin username." : "Bir yönetici kullanıcı adı yazın.", + "Set an admin password." : "Bir yönetici parolası yazın.", + "Can't create or write into the data directory %s" : "%s veri klasörü oluşturulamadı ya da içine yazılamadı", + "Invalid Federated Cloud ID" : "Birleşik Bulut Kimliği Geçersiz", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Paylaşım arka ucu %s OCP\\Share_Backend arayüzünü desteklemeli", + "Sharing backend %s not found" : "%s paylaşım arka ucu bulunamadı", + "Sharing backend for %s not found" : "%s için paylaşım arka ucu bulunamadı", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s sizinle »%2$s« ögesini paylaştı ve eklemenizi istiyor:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s sizinle »%2$s« ögesini paylaştı ve eklemenizi istiyor", + "»%s« added a note to a file shared with you" : "»%s« sizinle paylaştığı bir dosyaya bir not ekledi", + "Open »%s«" : "»%s« Aç", + "%1$s via %2$s" : "%1$s, %2$s aracılığıyla", + "You are not allowed to share %s" : "%s ögesini paylaşma izniniz yok", + "Can’t increase permissions of %s" : "%s izinleri arttırılamadı", + "Files can’t be shared with delete permissions" : "Silme izni ile dosya paylaşılamaz", + "Files can’t be shared with create permissions" : "Ekleme izni ile dosya paylaşılamaz", + "Expiration date is in the past" : "Son kullanma tarihi geçmişte", + "Can’t set expiration date more than %s days in the future" : "Son kullanma tarihi %sgünden sonrası olarak ayarlanamaz", + "%1$s shared »%2$s« with you" : "%1$s, sizinle »%2$s« ögesini paylaştı", + "%1$s shared »%2$s« with you." : "%1$s, sizinle »%2$s« ögesini paylaştı.", + "Click the button below to open it." : "Açmak için aşağıdaki düğmeye tıklayın.", + "The requested share does not exist anymore" : "Erişilmek istenilen paylaşım artık yok", + "Could not find category \"%s\"" : "\"%s\" kategorisi bulunamadı", + "Sunday" : "Pazar", + "Monday" : "Pazartesi", + "Tuesday" : "Salı", + "Wednesday" : "Çarşamba", + "Thursday" : "Perşembe", + "Friday" : "Cuma", + "Saturday" : "Cumartesi", + "Sun." : "Paz", + "Mon." : "Pzt", + "Tue." : "Sal", + "Wed." : "Çar", + "Thu." : "Per", + "Fri." : "Cum", + "Sat." : "Cmt", + "Su" : "Pa", + "Mo" : "Pt", + "Tu" : "Sa", + "We" : "Ça", + "Th" : "Pe", + "Fr" : "Cu", + "Sa" : "Ct", + "January" : "Ocak", + "February" : "Şubat", + "March" : "Mart", + "April" : "Nisan", + "May" : "Mayıs", + "June" : "Haziran", + "July" : "Temmuz", + "August" : "Ağustos", + "September" : "Eylül", + "October" : "Ekim", + "November" : "Kası", + "December" : "Aralı", + "Jan." : "Oca", + "Feb." : "Şub", + "Mar." : "Mar", + "Apr." : "Nis", + "May." : "May", + "Jun." : "Haz", + "Jul." : "Tem", + "Aug." : "Ağu", + "Sep." : "Eyl", + "Oct." : "Eki", + "Nov." : "Kas", + "Dec." : "Ara", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kullanıcı adında yalnız şu karakterler kullanılabilir: \"a-z\", \"A-Z\", \"0-9\", ve \"_.@-'\"", + "A valid username must be provided" : "Geçerli bir kullanıcı adı yazmalısınız", + "Username contains whitespace at the beginning or at the end" : "Kullanıcı adının başı ya da sonunda boşluk var", + "Username must not consist of dots only" : "Kullanıcı adı yalnız noktalardan oluşamaz", + "Username is invalid because files already exist for this user" : "Kullanıcı adı geçersiz, bu kullanıcı için zaten bazı dosyalar var", + "A valid password must be provided" : "Geçerli bir parola yazmalısınız", + "The username is already being used" : "Bu kullanıcı adı zaten var", + "Could not create user" : "Kullanıcı oluşturulamadı", + "User disabled" : "Kullanıcı devre dışı", + "Login canceled by app" : "Oturum açma uygulama tarafından iptal edildi", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "\"%1$s\" uygulaması, şu gereklilikler sağlanmadığı için kurulamıyor: %2$s", + "a safe home for all your data" : "verileriniz için güvenli bir barınak", + "File is currently busy, please try again later" : "Dosya şu anda meşgul, lütfen daha sonra deneyin", + "Can't read file" : "Dosya okunamadı", + "Application is not enabled" : "Uygulama etkinleştirilmemiş", + "Authentication error" : "Kimlik doğrulama sorunu", + "Token expired. Please reload page." : "Kodun süresi dolmuş. Lütfen sayfayı yenileyin.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Herhangi bir veritabanı sürücüsü (sqlite, mysql ya da postgresql) kurulmamış.", + "Cannot write into \"config\" directory" : "\"config\" klasörüne yazılamıyor", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "Bu sorun genellikle, web sunucusuna config klasörüne yazma izni verilerek çözülebilir. %s bölümüne bakın", + "Cannot write into \"apps\" directory" : "\"apps\" klasörüne yazılamıyor", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "Bu sorun genellikle, web sunucusuna apps klasörüne yazma izni verilerek ya da yapılandırma dosyasından uygulama mağazasını devre dışı bırakılarak çözülebilir.", + "Cannot create \"data\" directory" : "\"data\" klasörü oluşturulamadı", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Bu sorun genellikle, web sunucusuna kök klasöre yazma izni verilerek çözülebilir. %s bölümüne bakın", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "İzinler genellikle, web sunucusuna kök klasöre yazma izni verilerek düzeltilebilir. %s bölümüne bakın.", + "Setting locale to %s failed" : "Dil %s olarak ayarlanamadı", + "Please install one of these locales on your system and restart your webserver." : "Lütfen bu dillerden birini sisteminize kurun ve web sunucunuzu yeniden başlatın.", + "PHP module %s not installed." : "PHP %s modülü kurulmamış.", + "Please ask your server administrator to install the module." : "Lütfen modülü kurması için BT yöneticinizle görüşün.", + "PHP setting \"%s\" is not set to \"%s\"." : "\"%s\" PHP ayarı \"%s\" olarak ayarlanmamış.", + "Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini dosyasında bu ayar yapıldığında Nextcloud yeniden çalışır", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload, beklenen \"0\" değeri yerine \"%s\" olarak ayarlanmış", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Bu sorunu çözmek için php.ini dosyasındaki mbstring.func_overload seçeneğini 0 olarak ayarlayın", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 sürümü en az 2.7.0 olmalıdır. Şu anda %s kurulu.", + "To fix this issue update your libxml2 version and restart your web server." : "Bu sorunu çözmek için libxml2 sürümünüzü güncelleyin ve web sunucusunu yeniden başlatın.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP girintili doc bloklarını ayıklamak üzere yapılandırılmış gibi görünüyor. Bu durum bazı çekirdek uygulamalarına erişilmesini engelleyecek.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Bu sorun genellikle Zend OPcache ya da eAccelerator gibi bir ön bellek/hızlandırıcı nedeniyle ortaya çıkar.", + "PHP modules have been installed, but they are still listed as missing?" : "PHP modülleri kurulmuş, ancak hala eksik olarak mı görünüyor?", + "Please ask your server administrator to restart the web server." : "Lütfen web sunucusunu yeniden başlatması için BT yöneticinizle görüşün.", + "PostgreSQL >= 9 required" : "PostgreSQL >= 9 gerekli", + "Please upgrade your database version" : "Lütfen veritabanı sürümünüzü yükseltin", + "Your data directory is readable by other users" : "Veri klasörünüz diğer kullanıcılar tarafından okunabilir", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Lütfen izinleri 0770 olarak ayarlayarak diğer kullanıcıların klasörü görebilmesini sağlayın.", + "Your data directory must be an absolute path" : "Veri klasörünüz mutlak bir yol olmalıdır", + "Check the value of \"datadirectory\" in your configuration" : "Yapılandırmanızdaki \"datadirectory\" seçeneğini denetleyin", + "Your data directory is invalid" : "Veri klasörünüz geçersiz", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Veri klasörü kökünde \".ocdata\" adında bir dosya bulunduğundan emin olun.", + "Action \"%s\" not supported or implemented." : "\"%s\" işlemi desteklenmiyor ya da henüz kullanılamıyor.", + "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kodu hatalı", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "İsteğin tamamlanması için gerekli parametreler eksik: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kodu zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kodlu birleşik bulut hizmeti sağlayıcısı bulunamadı.", + "Could not obtain lock type %d on \"%s\"." : "\"%s\" için %d kilit türü alınamadı.", + "Storage unauthorized. %s" : "Depolamaya erişim izni yok. %s", + "Storage incomplete configuration. %s" : "Depolama yapılandırması tamamlanmamış. %s", + "Storage connection error. %s" : "Depolama bağlantısı sorunu. %s", + "Storage is temporarily not available" : "Depolama geçici olarak kullanılamıyor", + "Storage connection timeout. %s" : "Depolama bağlantısı zaman aşımı. %s", + "Following databases are supported: %s" : "Şu veritabanları destekleniyor: %s", + "Following platforms are supported: %s" : "Şu platformlar destekleniyor: %s", + "Overview" : "Özet", + "Basic settings" : "Temel Ayarlar", + "Sharing" : "Paylaşım", + "Security" : "Güvenlik", + "Groupware" : "Grup çalışması", + "Personal info" : "Kişisel Bilgiler", + "Mobile & desktop" : "Mobil ve masaüstü", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "Bu sorun genellikle, web sunucusuna apps klasörüne yazma izni verilerek ya da yapılandırma dosyasından uygulama mağazası devre dışı bırakılarak çözülebilir. %s bölümüne bakın" +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ug.js b/docker/overlays/nextcloud/html/lib/l10n/ug.js new file mode 100644 index 0000000..db9450e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ug.js @@ -0,0 +1,55 @@ +OC.L10N.register( + "lib", + { + "today" : "بۈگۈن", + "yesterday" : "تۈنۈگۈن", + "__language_name__" : "ئۇيغۇرچە", + "Help" : "ياردەم", + "Apps" : "ئەپلەر", + "Settings" : "تەڭشەكلەر", + "Log out" : "تىزىمدىن چىق", + "Users" : "ئىشلەتكۈچىلەر", + "Sunday" : "يەكشەنبە", + "Monday" : "دۈشەنبە", + "Tuesday" : "سەيشەنبە", + "Wednesday" : "چارشەنبە", + "Thursday" : "پەيشەنبە", + "Friday" : "جۈمە", + "Saturday" : "شەنبە", + "Sun." : "يەك", + "Mon." : "دۈش", + "Tue." : "سەي", + "Wed." : "چار", + "Thu." : "پەي", + "Fri." : "جۈم", + "Sat." : "شەن", + "January" : "قەھرىتان", + "February" : "ھۇت", + "March" : "نەۋرۇز", + "April" : "ئۇمۇت", + "May" : "باھار", + "June" : "سەپەر", + "July" : "چىللە", + "August" : "تومۇز", + "September" : "مىزان", + "October" : "ئوغۇز", + "November" : "ئوغلاق", + "December" : "كۆنەك", + "Jan." : "قەھرىتان", + "Feb." : "ھۇت", + "Mar." : "نەۋرۇز", + "Apr." : "ئۇمۇت", + "May." : "باھار", + "Jun." : "سەپەر", + "Jul." : "چىللە", + "Aug." : "تومۇز", + "Sep." : "مىزان", + "Oct." : "ئوغۇز", + "Nov." : "ئوغلاق", + "Dec." : "كۆنەك", + "A valid username must be provided" : "چوقۇم ئىناۋەتلىك ئىشلەتكۈچى ئىسمىدىن بىرنى تەمىنلەش كېرەك", + "A valid password must be provided" : "چوقۇم ئىناۋەتلىك ئىم تەمىنلەش كېرەك", + "Authentication error" : "سالاھىيەت دەلىللەش خاتالىقى", + "Sharing" : "ھەمبەھىر" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ug.json b/docker/overlays/nextcloud/html/lib/l10n/ug.json new file mode 100644 index 0000000..39ed0bd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ug.json @@ -0,0 +1,53 @@ +{ "translations": { + "today" : "بۈگۈن", + "yesterday" : "تۈنۈگۈن", + "__language_name__" : "ئۇيغۇرچە", + "Help" : "ياردەم", + "Apps" : "ئەپلەر", + "Settings" : "تەڭشەكلەر", + "Log out" : "تىزىمدىن چىق", + "Users" : "ئىشلەتكۈچىلەر", + "Sunday" : "يەكشەنبە", + "Monday" : "دۈشەنبە", + "Tuesday" : "سەيشەنبە", + "Wednesday" : "چارشەنبە", + "Thursday" : "پەيشەنبە", + "Friday" : "جۈمە", + "Saturday" : "شەنبە", + "Sun." : "يەك", + "Mon." : "دۈش", + "Tue." : "سەي", + "Wed." : "چار", + "Thu." : "پەي", + "Fri." : "جۈم", + "Sat." : "شەن", + "January" : "قەھرىتان", + "February" : "ھۇت", + "March" : "نەۋرۇز", + "April" : "ئۇمۇت", + "May" : "باھار", + "June" : "سەپەر", + "July" : "چىللە", + "August" : "تومۇز", + "September" : "مىزان", + "October" : "ئوغۇز", + "November" : "ئوغلاق", + "December" : "كۆنەك", + "Jan." : "قەھرىتان", + "Feb." : "ھۇت", + "Mar." : "نەۋرۇز", + "Apr." : "ئۇمۇت", + "May." : "باھار", + "Jun." : "سەپەر", + "Jul." : "چىللە", + "Aug." : "تومۇز", + "Sep." : "مىزان", + "Oct." : "ئوغۇز", + "Nov." : "ئوغلاق", + "Dec." : "كۆنەك", + "A valid username must be provided" : "چوقۇم ئىناۋەتلىك ئىشلەتكۈچى ئىسمىدىن بىرنى تەمىنلەش كېرەك", + "A valid password must be provided" : "چوقۇم ئىناۋەتلىك ئىم تەمىنلەش كېرەك", + "Authentication error" : "سالاھىيەت دەلىللەش خاتالىقى", + "Sharing" : "ھەمبەھىر" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/uk.js b/docker/overlays/nextcloud/html/lib/l10n/uk.js new file mode 100644 index 0000000..63072f0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/uk.js @@ -0,0 +1,174 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Не можу писати у каталог \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Зазвичай це можна виправити, надавши веб-серверу права на запис в теці конфігурації", + "See %s" : "Переглянути %s", + "Sample configuration detected" : "Виявлено приклад конфігурації", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Було виявлено, що приклад конфігурації було скопійовано. Це може нашкодити вашій системі та не підтримується. Будь ласка, зверніться до документації перед внесенням змін в файл config.php", + "%1$s and %2$s" : "%1$s та %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s та %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s та %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s та %5$s", + "PHP %s or higher is required." : "Необхідно PHP %s або вище", + "PHP with a version lower than %s is required." : "Потрібна версія PHP нижче %s ", + "The command line tool %s could not be found" : "Утиліту командного рядка %s не знайдено", + "The library %s is not available." : "Бібліотека %s недоступна.", + "Remote wipe started" : "Розпочато віддалене стирання", + "Remote wipe finished" : "Віддалене стирання завершено", + "Authentication" : "Автентифікація", + "Unknown filetype" : "Невідомий тип файлу", + "Invalid image" : "Невірне зображення", + "today" : "сьогодні", + "tomorrow" : "завтра", + "yesterday" : "вчора", + "_in %n day_::_in %n days_" : ["через %n день","через %n дні","через %n днів","через %n днів"], + "_%n day ago_::_%n days ago_" : ["%n день тому","%n днів тому","%n днів тому","%n днів тому"], + "next month" : "наступноого місяця", + "last month" : "минулого місяця", + "next year" : "наступного року", + "last year" : "минулого року", + "_%n year ago_::_%n years ago_" : ["%n рік тому","%n років тому","%n років тому","%n років тому"], + "in a few seconds" : "через кілька секунд", + "seconds ago" : "секунди тому", + "Empty file" : "Порожній файл", + "File name is a reserved word" : "Ім’я файлу є зарезервованим словом", + "File name contains at least one invalid character" : "Ім’я файлу містить принаймні один некоректний символ", + "File name is too long" : "Ім’я файлу занадто довге", + "Dot files are not allowed" : "Файли які починаються з крапки не допустимі", + "Empty filename is not allowed" : "Порожні імена файлів не допускаються", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Застосунок \"%s\" не може бути встановлений через те, що файл appinfo не може бути прочитано.", + "__language_name__" : "Українська", + "Help" : "Допомога", + "Apps" : "Застосунки", + "Settings" : "Налаштування", + "Log out" : "Вихід", + "Users" : "Користувачі", + "Unknown user" : "Невідомий користувач", + "Additional settings" : "Додаткові налаштування", + "%s enter the database username and name." : "%s введіть назву бази даних та ім'я користувача.", + "%s enter the database username." : "%s введіть ім'я користувача бази даних.", + "%s enter the database name." : "%s введіть назву бази даних.", + "%s you may not use dots in the database name" : "%s не можна використовувати крапки в назві бази даних", + "Oracle connection could not be established" : "Не можемо з'єднатися з Oracle ", + "Oracle username and/or password not valid" : "Oracle ім'я користувача та/або пароль не дійсні", + "PostgreSQL username and/or password not valid" : "PostgreSQL ім'я користувача та/або пароль не дійсні", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не підтримується і %s не буде коректно працювати на цій платформі. Випробовуєте на свій риск!", + "For the best results, please consider using a GNU/Linux server instead." : "Для кращих результатів розгляньте можливість використання GNU/Linux серверу", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Здається що екземпляр цього %s працює в 32-бітному PHP середовищі і open_basedir повинен бути налаштований в php.ini. Це призведе до проблем з файлами більше 4 ГБ і це дуже не рекомендується.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Будь ласка, вилучіть параметр open_basedir у вашому php.ini або перейдіть на 64-бітний PHP.", + "Set an admin username." : "Встановіть ім'я адміністратора.", + "Set an admin password." : "Встановіть пароль адміністратора.", + "Can't create or write into the data directory %s" : "Неможливо створити або записати каталог даних %s", + "Invalid Federated Cloud ID" : "Неправильний Об'єднаний Хмарний Ідентіфікатор ", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Бекенд спільного доступу %s повинен реалізовувати інтерфейс OCP\\Share_Backend", + "Sharing backend %s not found" : "Бекенд спільного доступу %s не знайдено", + "Sharing backend for %s not found" : "Бекенд спільного доступу для %s не знайдено", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s надав доступ »%2$s« та хоче додати:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s надав доступ »%2$s« та хоче додати", + "»%s« added a note to a file shared with you" : "»%s« додав нотатку до опублікованого файлу", + "Open »%s«" : "Відкрити 1%s", + "%1$s via %2$s" : "%1$s через %2$s", + "You are not allowed to share %s" : "Вам заборонено поширювати %s", + "Expiration date is in the past" : "Дата закінчення в минулому", + "%1$s shared »%2$s« with you" : "%1$s надав доступ до »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s надав доступ до »%2$s«.", + "Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.", + "Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"", + "Sunday" : "Неділя", + "Monday" : "Понеділок", + "Tuesday" : "Вівторок", + "Wednesday" : "Середа", + "Thursday" : "Четвер", + "Friday" : "П'ятниця", + "Saturday" : "Субота", + "Sun." : "Нд.", + "Mon." : "Пн.", + "Tue." : "Вт.", + "Wed." : "Ср.", + "Thu." : "Чт.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "Su" : "Нд.", + "Mo" : "Пн.", + "Tu" : "Вт.", + "We" : "Ср.", + "Th" : "Чт.", + "Fr" : "Пт.", + "Sa" : "Сб.", + "January" : "Січень", + "February" : "Лютий", + "March" : "Березень", + "April" : "Квітень", + "May" : "Травень", + "June" : "Червень", + "July" : "Липень", + "August" : "Серпень", + "September" : "Вересень", + "October" : "Жовтень", + "November" : "Листопад", + "December" : "Грудень", + "Jan." : "Січ.", + "Feb." : "Лют.", + "Mar." : "Бер.", + "Apr." : "Кві.", + "May." : "Тра.", + "Jun." : "Чер.", + "Jul." : "Лип.", + "Aug." : "Сер.", + "Sep." : "Вер.", + "Oct." : "Жов.", + "Nov." : "Лис.", + "Dec." : "Гру.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"", + "A valid username must be provided" : "Потрібно задати вірне ім'я користувача", + "Username contains whitespace at the beginning or at the end" : "Ім'я користувача містить символ пробілу в початку або в кінці", + "A valid password must be provided" : "Потрібно задати вірний пароль", + "The username is already being used" : "Ім'я користувача вже використовується", + "User disabled" : "Користувач виключений", + "Login canceled by app" : "Вхід скасовано застосунком", + "a safe home for all your data" : "безпечний дім для ваших даних", + "File is currently busy, please try again later" : "Файл на разі зайнятий, будь ласка, спробуйте пізніше", + "Can't read file" : "Не можливо прочитати файл", + "Application is not enabled" : "Застосунок не увімкнено", + "Authentication error" : "Помилка автентифікації", + "Token expired. Please reload page." : "Строк дії токена скінчився. Будь ласка, перезавантажте сторінку.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Не встановлено драйвер бази даних (sqlite, mysql, or postgresql).", + "Cannot write into \"config\" directory" : "Не можу писати у теку \"config\"", + "Cannot write into \"apps\" directory" : "Не можу писати у теку \"apps\"", + "Cannot create \"data\" directory" : "Неможливо створити каталог даних", + "Setting locale to %s failed" : "Установка локалі %s не вдалася", + "Please install one of these locales on your system and restart your webserver." : "Встановіть один із цих мовних пакетів в вашу систему і перезапустіть веб-сервер.", + "PHP module %s not installed." : "%s модуль PHP не встановлено.", + "Please ask your server administrator to install the module." : "Будь ласка, зверніться до адміністратора, щоб встановити модуль.", + "PHP setting \"%s\" is not set to \"%s\"." : "Параметр PHP \"%s\" не встановлено в \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload налаштовано як \"%s\" замість очікуваного значення \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Для виправлення змініть mbstring.func_overload на 0 у вашому php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Необхідно libxml2 версії принаймні 2.7.0. На разі встановлена %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Що виправити це оновіть версію libxml2 та перезапустіть веб-сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Схоже, що PHP налаштовано на вичищення блоків вбудованої документації. Це зробить кілька основних додатків недоступними.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Це, ймовірно, обумовлено використанням кеша/прискорювача такого як Zend OPcache або eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Модулі PHP були встановлені, але вони все ще перераховані як відсутні?", + "Please ask your server administrator to restart the web server." : "Будь ласка, зверніться до адміністратора, щоб перезавантажити сервер.", + "PostgreSQL >= 9 required" : "Потрібно PostgreSQL> = 9", + "Please upgrade your database version" : "Оновіть версію бази даних", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Змініть права доступу на 0770, щоб інші користувачі не могли отримати список файлів цього каталогу.", + "Check the value of \"datadirectory\" in your configuration" : "Перевірте значення \"datadirectory\" у своїй конфігурації", + "Your data directory is invalid" : "Ваш каталог даних недійсний", + "Could not obtain lock type %d on \"%s\"." : "Не вдалося отримати блокування типу %d для \"%s\"", + "Storage unauthorized. %s" : "Сховище не авторизовано. %s", + "Storage incomplete configuration. %s" : "Неповна конфігурація сховища. %s", + "Storage connection error. %s" : "Помилка з'єднання зі сховищем. %s", + "Storage is temporarily not available" : "Сховище тимчасово недоступне", + "Storage connection timeout. %s" : "Час під'єднання до сховища вичерпався. %s", + "Following databases are supported: %s" : "Підтримуються наступні сервери баз даних: %s", + "Following platforms are supported: %s" : "Підтримуються наступні платформи: %s", + "Overview" : "Огляд", + "Basic settings" : "Основні налаштування", + "Sharing" : "Спільне", + "Security" : "Безпека", + "Groupware" : "Робочі групи", + "Personal info" : "Особиста інформація", + "Mobile & desktop" : "Смартфон та ноутбук" +}, +"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/uk.json b/docker/overlays/nextcloud/html/lib/l10n/uk.json new file mode 100644 index 0000000..f3647ca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/uk.json @@ -0,0 +1,172 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Не можу писати у каталог \"config\"!", + "This can usually be fixed by giving the webserver write access to the config directory" : "Зазвичай це можна виправити, надавши веб-серверу права на запис в теці конфігурації", + "See %s" : "Переглянути %s", + "Sample configuration detected" : "Виявлено приклад конфігурації", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Було виявлено, що приклад конфігурації було скопійовано. Це може нашкодити вашій системі та не підтримується. Будь ласка, зверніться до документації перед внесенням змін в файл config.php", + "%1$s and %2$s" : "%1$s та %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s та %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s та %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s та %5$s", + "PHP %s or higher is required." : "Необхідно PHP %s або вище", + "PHP with a version lower than %s is required." : "Потрібна версія PHP нижче %s ", + "The command line tool %s could not be found" : "Утиліту командного рядка %s не знайдено", + "The library %s is not available." : "Бібліотека %s недоступна.", + "Remote wipe started" : "Розпочато віддалене стирання", + "Remote wipe finished" : "Віддалене стирання завершено", + "Authentication" : "Автентифікація", + "Unknown filetype" : "Невідомий тип файлу", + "Invalid image" : "Невірне зображення", + "today" : "сьогодні", + "tomorrow" : "завтра", + "yesterday" : "вчора", + "_in %n day_::_in %n days_" : ["через %n день","через %n дні","через %n днів","через %n днів"], + "_%n day ago_::_%n days ago_" : ["%n день тому","%n днів тому","%n днів тому","%n днів тому"], + "next month" : "наступноого місяця", + "last month" : "минулого місяця", + "next year" : "наступного року", + "last year" : "минулого року", + "_%n year ago_::_%n years ago_" : ["%n рік тому","%n років тому","%n років тому","%n років тому"], + "in a few seconds" : "через кілька секунд", + "seconds ago" : "секунди тому", + "Empty file" : "Порожній файл", + "File name is a reserved word" : "Ім’я файлу є зарезервованим словом", + "File name contains at least one invalid character" : "Ім’я файлу містить принаймні один некоректний символ", + "File name is too long" : "Ім’я файлу занадто довге", + "Dot files are not allowed" : "Файли які починаються з крапки не допустимі", + "Empty filename is not allowed" : "Порожні імена файлів не допускаються", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "Застосунок \"%s\" не може бути встановлений через те, що файл appinfo не може бути прочитано.", + "__language_name__" : "Українська", + "Help" : "Допомога", + "Apps" : "Застосунки", + "Settings" : "Налаштування", + "Log out" : "Вихід", + "Users" : "Користувачі", + "Unknown user" : "Невідомий користувач", + "Additional settings" : "Додаткові налаштування", + "%s enter the database username and name." : "%s введіть назву бази даних та ім'я користувача.", + "%s enter the database username." : "%s введіть ім'я користувача бази даних.", + "%s enter the database name." : "%s введіть назву бази даних.", + "%s you may not use dots in the database name" : "%s не можна використовувати крапки в назві бази даних", + "Oracle connection could not be established" : "Не можемо з'єднатися з Oracle ", + "Oracle username and/or password not valid" : "Oracle ім'я користувача та/або пароль не дійсні", + "PostgreSQL username and/or password not valid" : "PostgreSQL ім'я користувача та/або пароль не дійсні", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X не підтримується і %s не буде коректно працювати на цій платформі. Випробовуєте на свій риск!", + "For the best results, please consider using a GNU/Linux server instead." : "Для кращих результатів розгляньте можливість використання GNU/Linux серверу", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Здається що екземпляр цього %s працює в 32-бітному PHP середовищі і open_basedir повинен бути налаштований в php.ini. Це призведе до проблем з файлами більше 4 ГБ і це дуже не рекомендується.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Будь ласка, вилучіть параметр open_basedir у вашому php.ini або перейдіть на 64-бітний PHP.", + "Set an admin username." : "Встановіть ім'я адміністратора.", + "Set an admin password." : "Встановіть пароль адміністратора.", + "Can't create or write into the data directory %s" : "Неможливо створити або записати каталог даних %s", + "Invalid Federated Cloud ID" : "Неправильний Об'єднаний Хмарний Ідентіфікатор ", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Бекенд спільного доступу %s повинен реалізовувати інтерфейс OCP\\Share_Backend", + "Sharing backend %s not found" : "Бекенд спільного доступу %s не знайдено", + "Sharing backend for %s not found" : "Бекенд спільного доступу для %s не знайдено", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s надав доступ »%2$s« та хоче додати:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s надав доступ »%2$s« та хоче додати", + "»%s« added a note to a file shared with you" : "»%s« додав нотатку до опублікованого файлу", + "Open »%s«" : "Відкрити 1%s", + "%1$s via %2$s" : "%1$s через %2$s", + "You are not allowed to share %s" : "Вам заборонено поширювати %s", + "Expiration date is in the past" : "Дата закінчення в минулому", + "%1$s shared »%2$s« with you" : "%1$s надав доступ до »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s надав доступ до »%2$s«.", + "Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.", + "Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"", + "Sunday" : "Неділя", + "Monday" : "Понеділок", + "Tuesday" : "Вівторок", + "Wednesday" : "Середа", + "Thursday" : "Четвер", + "Friday" : "П'ятниця", + "Saturday" : "Субота", + "Sun." : "Нд.", + "Mon." : "Пн.", + "Tue." : "Вт.", + "Wed." : "Ср.", + "Thu." : "Чт.", + "Fri." : "Пт.", + "Sat." : "Сб.", + "Su" : "Нд.", + "Mo" : "Пн.", + "Tu" : "Вт.", + "We" : "Ср.", + "Th" : "Чт.", + "Fr" : "Пт.", + "Sa" : "Сб.", + "January" : "Січень", + "February" : "Лютий", + "March" : "Березень", + "April" : "Квітень", + "May" : "Травень", + "June" : "Червень", + "July" : "Липень", + "August" : "Серпень", + "September" : "Вересень", + "October" : "Жовтень", + "November" : "Листопад", + "December" : "Грудень", + "Jan." : "Січ.", + "Feb." : "Лют.", + "Mar." : "Бер.", + "Apr." : "Кві.", + "May." : "Тра.", + "Jun." : "Чер.", + "Jul." : "Лип.", + "Aug." : "Сер.", + "Sep." : "Вер.", + "Oct." : "Жов.", + "Nov." : "Лис.", + "Dec." : "Гру.", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"", + "A valid username must be provided" : "Потрібно задати вірне ім'я користувача", + "Username contains whitespace at the beginning or at the end" : "Ім'я користувача містить символ пробілу в початку або в кінці", + "A valid password must be provided" : "Потрібно задати вірний пароль", + "The username is already being used" : "Ім'я користувача вже використовується", + "User disabled" : "Користувач виключений", + "Login canceled by app" : "Вхід скасовано застосунком", + "a safe home for all your data" : "безпечний дім для ваших даних", + "File is currently busy, please try again later" : "Файл на разі зайнятий, будь ласка, спробуйте пізніше", + "Can't read file" : "Не можливо прочитати файл", + "Application is not enabled" : "Застосунок не увімкнено", + "Authentication error" : "Помилка автентифікації", + "Token expired. Please reload page." : "Строк дії токена скінчився. Будь ласка, перезавантажте сторінку.", + "No database drivers (sqlite, mysql, or postgresql) installed." : "Не встановлено драйвер бази даних (sqlite, mysql, or postgresql).", + "Cannot write into \"config\" directory" : "Не можу писати у теку \"config\"", + "Cannot write into \"apps\" directory" : "Не можу писати у теку \"apps\"", + "Cannot create \"data\" directory" : "Неможливо створити каталог даних", + "Setting locale to %s failed" : "Установка локалі %s не вдалася", + "Please install one of these locales on your system and restart your webserver." : "Встановіть один із цих мовних пакетів в вашу систему і перезапустіть веб-сервер.", + "PHP module %s not installed." : "%s модуль PHP не встановлено.", + "Please ask your server administrator to install the module." : "Будь ласка, зверніться до адміністратора, щоб встановити модуль.", + "PHP setting \"%s\" is not set to \"%s\"." : "Параметр PHP \"%s\" не встановлено в \"%s\".", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload налаштовано як \"%s\" замість очікуваного значення \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "Для виправлення змініть mbstring.func_overload на 0 у вашому php.ini", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "Необхідно libxml2 версії принаймні 2.7.0. На разі встановлена %s.", + "To fix this issue update your libxml2 version and restart your web server." : "Що виправити це оновіть версію libxml2 та перезапустіть веб-сервер.", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Схоже, що PHP налаштовано на вичищення блоків вбудованої документації. Це зробить кілька основних додатків недоступними.", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Це, ймовірно, обумовлено використанням кеша/прискорювача такого як Zend OPcache або eAccelerator.", + "PHP modules have been installed, but they are still listed as missing?" : "Модулі PHP були встановлені, але вони все ще перераховані як відсутні?", + "Please ask your server administrator to restart the web server." : "Будь ласка, зверніться до адміністратора, щоб перезавантажити сервер.", + "PostgreSQL >= 9 required" : "Потрібно PostgreSQL> = 9", + "Please upgrade your database version" : "Оновіть версію бази даних", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Змініть права доступу на 0770, щоб інші користувачі не могли отримати список файлів цього каталогу.", + "Check the value of \"datadirectory\" in your configuration" : "Перевірте значення \"datadirectory\" у своїй конфігурації", + "Your data directory is invalid" : "Ваш каталог даних недійсний", + "Could not obtain lock type %d on \"%s\"." : "Не вдалося отримати блокування типу %d для \"%s\"", + "Storage unauthorized. %s" : "Сховище не авторизовано. %s", + "Storage incomplete configuration. %s" : "Неповна конфігурація сховища. %s", + "Storage connection error. %s" : "Помилка з'єднання зі сховищем. %s", + "Storage is temporarily not available" : "Сховище тимчасово недоступне", + "Storage connection timeout. %s" : "Час під'єднання до сховища вичерпався. %s", + "Following databases are supported: %s" : "Підтримуються наступні сервери баз даних: %s", + "Following platforms are supported: %s" : "Підтримуються наступні платформи: %s", + "Overview" : "Огляд", + "Basic settings" : "Основні налаштування", + "Sharing" : "Спільне", + "Security" : "Безпека", + "Groupware" : "Робочі групи", + "Personal info" : "Особиста інформація", + "Mobile & desktop" : "Смартфон та ноутбук" +},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/ur_PK.js b/docker/overlays/nextcloud/html/lib/l10n/ur_PK.js new file mode 100644 index 0000000..9fb8b0d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ur_PK.js @@ -0,0 +1,37 @@ +OC.L10N.register( + "lib", + { + "Unknown filetype" : "غیر معرروف قسم کی فائل", + "Invalid image" : "غلط تصویر", + "today" : "آج", + "yesterday" : "کل", + "last month" : "پچھلے مہنیے", + "last year" : "پچھلے سال", + "seconds ago" : "سیکنڈز پہلے", + "__language_name__" : "اردو", + "Help" : "مدد", + "Apps" : "ایپز", + "Settings" : "سیٹینگز", + "Log out" : "لاگ آؤٹ", + "Users" : "یوزرز", + "Sunday" : "اتوار", + "Monday" : "سوموار", + "Tuesday" : "منگل", + "Wednesday" : "بدھ", + "Thursday" : "جمعرات", + "Friday" : "جمعہ", + "Saturday" : "ہفتہ", + "January" : "جنوری", + "February" : "فرورئ", + "March" : "مارچ", + "April" : "اپریل", + "May" : "مئی", + "June" : "جون", + "July" : "جولائی", + "August" : "اگست", + "September" : "ستمبر", + "October" : "اکتوبر", + "November" : "نومبر", + "December" : "دسمبر" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/ur_PK.json b/docker/overlays/nextcloud/html/lib/l10n/ur_PK.json new file mode 100644 index 0000000..6e04014 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/ur_PK.json @@ -0,0 +1,35 @@ +{ "translations": { + "Unknown filetype" : "غیر معرروف قسم کی فائل", + "Invalid image" : "غلط تصویر", + "today" : "آج", + "yesterday" : "کل", + "last month" : "پچھلے مہنیے", + "last year" : "پچھلے سال", + "seconds ago" : "سیکنڈز پہلے", + "__language_name__" : "اردو", + "Help" : "مدد", + "Apps" : "ایپز", + "Settings" : "سیٹینگز", + "Log out" : "لاگ آؤٹ", + "Users" : "یوزرز", + "Sunday" : "اتوار", + "Monday" : "سوموار", + "Tuesday" : "منگل", + "Wednesday" : "بدھ", + "Thursday" : "جمعرات", + "Friday" : "جمعہ", + "Saturday" : "ہفتہ", + "January" : "جنوری", + "February" : "فرورئ", + "March" : "مارچ", + "April" : "اپریل", + "May" : "مئی", + "June" : "جون", + "July" : "جولائی", + "August" : "اگست", + "September" : "ستمبر", + "October" : "اکتوبر", + "November" : "نومبر", + "December" : "دسمبر" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/uz.js b/docker/overlays/nextcloud/html/lib/l10n/uz.js new file mode 100644 index 0000000..207139a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/uz.js @@ -0,0 +1,16 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "\"Config\" katalogiga yozish mumkin emas!", + "Unknown filetype" : "Noma'lum filetype", + "Invalid image" : "Tasdiqlanmagan tasvir", + "seconds ago" : "soniya oldin", + "Help" : "Yordam", + "Apps" : "Ilovalar", + "Settings" : "Sozlamalar", + "Users" : "Foydalanuvchilar", + "Unknown user" : "Noma'lum foydalanuvchi", + "January" : "Yanvar", + "Storage is temporarily not available" : "Saqlash vaqti-vaqti bilan mavjud emas" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/uz.json b/docker/overlays/nextcloud/html/lib/l10n/uz.json new file mode 100644 index 0000000..59f1582 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/uz.json @@ -0,0 +1,14 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "\"Config\" katalogiga yozish mumkin emas!", + "Unknown filetype" : "Noma'lum filetype", + "Invalid image" : "Tasdiqlanmagan tasvir", + "seconds ago" : "soniya oldin", + "Help" : "Yordam", + "Apps" : "Ilovalar", + "Settings" : "Sozlamalar", + "Users" : "Foydalanuvchilar", + "Unknown user" : "Noma'lum foydalanuvchi", + "January" : "Yanvar", + "Storage is temporarily not available" : "Saqlash vaqti-vaqti bilan mavjud emas" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/vi.js b/docker/overlays/nextcloud/html/lib/l10n/vi.js new file mode 100644 index 0000000..a3c920a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/vi.js @@ -0,0 +1,74 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "Không thể ghi vào thư mục \"config\"!", + "See %s" : "Xem %s", + "Unknown filetype" : "Không biết kiểu tập tin", + "Invalid image" : "Hình ảnh không hợp lệ", + "today" : "hôm nay", + "yesterday" : "hôm qua", + "last month" : "tháng trước", + "_%n month ago_::_%n months ago_" : ["%n tháng trước"], + "last year" : "năm trước", + "_%n hour ago_::_%n hours ago_" : ["%n giờ trước"], + "_%n minute ago_::_%n minutes ago_" : ["%n phút trước"], + "seconds ago" : "vài giây trước", + "__language_name__" : "Tiếng Việt", + "Help" : "Giúp đỡ", + "Apps" : "Ứng dụng", + "Settings" : "Thiết lập", + "Log out" : "Đăng xuất", + "Users" : "Người dùng", + "Unknown user" : "Người dùng không tồn tại", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.", + "Open »%s«" : "Mở »%s«", + "%1$s via %2$s" : "%1$s thông qua %2$s", + "Could not find category \"%s\"" : "không thể tìm thấy mục \"%s\"", + "Sunday" : "Chủ nhật", + "Monday" : "Thứ 2", + "Tuesday" : "Thứ 3", + "Wednesday" : "Thứ 4", + "Thursday" : "Thứ 5", + "Friday" : "Thứ ", + "Saturday" : "Thứ 7", + "Sun." : "Chủ nhật", + "Mon." : "Thứ hai", + "Tue." : "Thứ ba", + "Wed." : "Thứ tư", + "Thu." : "Thứ năm", + "Fri." : "Thứ sáu", + "Sat." : "Thứ bảy", + "January" : "Tháng 1", + "February" : "Tháng 2", + "March" : "Tháng 3", + "April" : "Tháng 4", + "May" : "Tháng 5", + "June" : "Tháng 6", + "July" : "Tháng 7", + "August" : "Tháng 8", + "September" : "Tháng 9", + "October" : "Tháng 10", + "November" : "Tháng 11", + "December" : "Tháng 12", + "Jan." : "Tháng 1", + "Feb." : "Tháng 2", + "Mar." : "Tháng 3", + "Apr." : "Tháng 4", + "May." : "Tháng 5", + "Jun." : "Tháng 6", + "Jul." : "Tháng 7", + "Aug." : "Tháng 8", + "Sep." : "Tháng 9", + "Oct." : "Tháng 10", + "Nov." : "Tháng 11", + "Dec." : "Tháng 12", + "User disabled" : "Vô hiệu hóa sử dụng", + "a safe home for all your data" : "Một ngôi nhà an toàn cho toàn bộ dữ liệu của bạn", + "Application is not enabled" : "Ứng dụng không được BẬT", + "Authentication error" : "Lỗi xác thực", + "Token expired. Please reload page." : "Mã Token đã hết hạn. Hãy tải lại trang.", + "Storage is temporarily not available" : "Kho lưu trữ tạm thời không khả dụng", + "Sharing" : "Đang chia sẽ", + "Security" : "Bảo mật" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/vi.json b/docker/overlays/nextcloud/html/lib/l10n/vi.json new file mode 100644 index 0000000..5ec3530 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/vi.json @@ -0,0 +1,72 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "Không thể ghi vào thư mục \"config\"!", + "See %s" : "Xem %s", + "Unknown filetype" : "Không biết kiểu tập tin", + "Invalid image" : "Hình ảnh không hợp lệ", + "today" : "hôm nay", + "yesterday" : "hôm qua", + "last month" : "tháng trước", + "_%n month ago_::_%n months ago_" : ["%n tháng trước"], + "last year" : "năm trước", + "_%n hour ago_::_%n hours ago_" : ["%n giờ trước"], + "_%n minute ago_::_%n minutes ago_" : ["%n phút trước"], + "seconds ago" : "vài giây trước", + "__language_name__" : "Tiếng Việt", + "Help" : "Giúp đỡ", + "Apps" : "Ứng dụng", + "Settings" : "Thiết lập", + "Log out" : "Đăng xuất", + "Users" : "Người dùng", + "Unknown user" : "Người dùng không tồn tại", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.", + "Open »%s«" : "Mở »%s«", + "%1$s via %2$s" : "%1$s thông qua %2$s", + "Could not find category \"%s\"" : "không thể tìm thấy mục \"%s\"", + "Sunday" : "Chủ nhật", + "Monday" : "Thứ 2", + "Tuesday" : "Thứ 3", + "Wednesday" : "Thứ 4", + "Thursday" : "Thứ 5", + "Friday" : "Thứ ", + "Saturday" : "Thứ 7", + "Sun." : "Chủ nhật", + "Mon." : "Thứ hai", + "Tue." : "Thứ ba", + "Wed." : "Thứ tư", + "Thu." : "Thứ năm", + "Fri." : "Thứ sáu", + "Sat." : "Thứ bảy", + "January" : "Tháng 1", + "February" : "Tháng 2", + "March" : "Tháng 3", + "April" : "Tháng 4", + "May" : "Tháng 5", + "June" : "Tháng 6", + "July" : "Tháng 7", + "August" : "Tháng 8", + "September" : "Tháng 9", + "October" : "Tháng 10", + "November" : "Tháng 11", + "December" : "Tháng 12", + "Jan." : "Tháng 1", + "Feb." : "Tháng 2", + "Mar." : "Tháng 3", + "Apr." : "Tháng 4", + "May." : "Tháng 5", + "Jun." : "Tháng 6", + "Jul." : "Tháng 7", + "Aug." : "Tháng 8", + "Sep." : "Tháng 9", + "Oct." : "Tháng 10", + "Nov." : "Tháng 11", + "Dec." : "Tháng 12", + "User disabled" : "Vô hiệu hóa sử dụng", + "a safe home for all your data" : "Một ngôi nhà an toàn cho toàn bộ dữ liệu của bạn", + "Application is not enabled" : "Ứng dụng không được BẬT", + "Authentication error" : "Lỗi xác thực", + "Token expired. Please reload page." : "Mã Token đã hết hạn. Hãy tải lại trang.", + "Storage is temporarily not available" : "Kho lưu trữ tạm thời không khả dụng", + "Sharing" : "Đang chia sẽ", + "Security" : "Bảo mật" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_CN.js b/docker/overlays/nextcloud/html/lib/l10n/zh_CN.js new file mode 100644 index 0000000..aed3182 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_CN.js @@ -0,0 +1,238 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!", + "This can usually be fixed by giving the webserver write access to the config directory" : "您可以设置 Web 服务器对 config 目录的写权限修复这个问题", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "或者,如果希望保持 config.php 文件的只读权限,请将 \"config_is_read_only\" 设置为 true。", + "See %s" : "查看 %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "通常可以通过授予 Web 服务器对 config 目录的写访问权限来解决此问题。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "或者,如果希望保持 config.php 文件的只读权限,请将 \"config_is_read_only\" 设置为 true。查看 %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "应用%1$s的文件替换不正确。请确认版本与当前服务器兼容。", + "Sample configuration detected" : "示例配置检测", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。", + "%1$s and %2$s" : "%1$s 和 %2$s", + "%1$s, %2$s and %3$s" : "%1$s,%2$s 和 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s,%2$s,%3$s 和 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s,%2$s,%3$s,%4$s 和 %5$s", + "Education Edition" : "教育版", + "Enterprise bundle" : "企业捆绑包", + "Groupware bundle" : "群组捆绑包", + "Hub bundle" : "枢纽捆绑包", + "Social sharing bundle" : "社交共享捆绑包", + "PHP %s or higher is required." : "要求 PHP 版本 %s 或者更高。", + "PHP with a version lower than %s is required." : "需要版本低于 %s 的PHP。", + "%sbit or higher PHP required." : "需要 %s 或更高版本的 PHP。", + "The following databases are supported: %s" : "支持以下数据库:%s", + "The command line tool %s could not be found" : "命令行工具 %s 未找到", + "The library %s is not available." : "库文件 %s 不可用", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "库版本 %1$s 高于需要的版本 %2$s - 可用版本 %3$s。", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "库版本 %1$s 低于需要的版本 %2$s - 可用版本 %3$s。", + "The following platforms are supported: %s" : "支持以下平台:%s", + "Server version %s or higher is required." : "需要服务器版本 %s 或更高版本。", + "Server version %s or lower is required." : "需要服务器版本 %s 或更低版本。", + "Logged in user must be an admin or sub admin" : "当前登录用户必须为管理员或子管理员", + "Logged in user must be an admin" : "当前登录用户必须为管理员", + "Wiping of device %s has started" : "设备%s的擦除操作已开始", + "Wiping of device »%s« has started" : "设备 »%s« 的擦除操作已开始", + "»%s« started remote wipe" : "»%s« 已开始远程擦除", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "设备或应用程序 »%s« 已开始远程擦除进程。进程完成后您将收到另一封邮件。", + "Wiping of device %s has finished" : "设备 %s 的擦除已完成", + "Wiping of device »%s« has finished" : "设备 »%s« 的擦除操作已完成", + "»%s« finished remote wipe" : "»%s« 已完成远程擦除", + "Device or application »%s« has finished the remote wipe process." : "设备或应用程序 »%s« 已完成远程擦除进程。", + "Remote wipe started" : "远程擦除开始", + "A remote wipe was started on device %s" : "设备%s上一个远程擦除操作已开始", + "Remote wipe finished" : "远程擦除完成", + "The remote wipe on %s has finished" : "%s上的远程擦除操作已完成", + "Authentication" : "验证", + "Unknown filetype" : "未知的文件类型", + "Invalid image" : "无效的图像", + "Avatar image is not square" : "头像图像不是正方形", + "today" : "今天", + "tomorrow" : "明天", + "yesterday" : "昨天", + "_in %n day_::_in %n days_" : ["%n天内"], + "_%n day ago_::_%n days ago_" : ["%n 天前"], + "next month" : "下月", + "last month" : "上月", + "_in %n month_::_in %n months_" : ["%n月内"], + "_%n month ago_::_%n months ago_" : ["%n 月前"], + "next year" : "明年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n年内"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n小时内"], + "_%n hour ago_::_%n hours ago_" : ["%n 小时前"], + "_in %n minute_::_in %n minutes_" : ["%n分钟内"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分钟前"], + "in a few seconds" : "几秒钟内", + "seconds ago" : "几秒前", + "Empty file" : "空文件", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "模块:%s不存在。请在 App 设置中开启或联系管理员。", + "File name is a reserved word" : "文件名包含敏感字符", + "File name contains at least one invalid character" : "文件名中存在至少一个非法字符", + "File name is too long" : "文件名过长", + "Dot files are not allowed" : "以 . 开头的文件不被允许", + "Empty filename is not allowed" : "不允许使用空名称", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "无法安装应用\"%s\",因为无法读取appinfo文件。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "应用程式 \"%s\" 无法安装,因为它与这个版本的服务器不兼容。", + "__language_name__" : "简体中文", + "This is an automatically sent email, please do not reply." : "这是一个自动生成的电子邮件,请不要回复。", + "Help" : "帮助", + "Apps" : "应用", + "Settings" : "设置", + "Log out" : "登出", + "Users" : "用户", + "Unknown user" : "未知用户", + "Additional settings" : "其他设置", + "%s enter the database username and name." : "%s 输入数据库用户名和名称。", + "%s enter the database username." : "%s 输入数据库用户名。", + "%s enter the database name." : "%s 输入数据库名称。", + "%s you may not use dots in the database name" : "%s 您不能在数据库名称中使用英文句号。", + "MySQL username and/or password not valid" : "MySQL数据库用户名和/或密码无效", + "You need to enter details of an existing account." : "您需要输入现有账号的详细信息。", + "Oracle connection could not be established" : "不能建立甲骨文连接", + "Oracle username and/or password not valid" : "Oracle 数据库用户名和/或密码无效", + "PostgreSQL username and/or password not valid" : "PostgreSQL 数据库用户名和/或密码无效", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X 不被支持并且 %s 在这个平台上无法正常工作。请自行承担风险!", + "For the best results, please consider using a GNU/Linux server instead." : "为了达到最好的效果,请考虑使用 GNU/Linux 服务器。", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "看起来这个 %s 实例运行在32位PHP环境中并且已在php.ini中配置open_basedir。这将在文件超过4GB时出现问题,我们极力反对这样做。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "请删除php.ini中的open_basedir设置或切换到64位PHP。", + "Set an admin username." : "请设置一个管理员用户名。", + "Set an admin password." : "请设置一个管理员密码。", + "Can't create or write into the data directory %s" : "无法创建或写入数据目录 %s", + "Invalid Federated Cloud ID" : "无效的联合云ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "共享后端 %s 必须实现 OCP\\Share_Backend 接口", + "Sharing backend %s not found" : "%s 的共享后端未找到", + "Sharing backend for %s not found" : "%s 的共享后端未找到", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s 与您共享了 »%2$s« 并希望添加:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s 与您共享了 »%2$s« 并希望添加", + "»%s« added a note to a file shared with you" : "»%s« 在与您共享的文件中添加了备注", + "Open »%s«" : "打开 »%s«", + "%1$s via %2$s" : "%1$s 通过 %2$s", + "You are not allowed to share %s" : "您无权共享 %s", + "Can’t increase permissions of %s" : "无法增加%s的权限。", + "Files can’t be shared with delete permissions" : "无法共享有删除权限的文件", + "Files can’t be shared with create permissions" : "无法共享有创建权限的文件", + "Expiration date is in the past" : "到期日期已过", + "Can’t set expiration date more than %s days in the future" : "无法将过期日期设置为超过 %s 天。", + "%1$s shared »%2$s« with you" : "%1$s 对您共享了 »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s 对您共享了 »%2$s«。", + "Click the button below to open it." : "点击下方按钮可打开它。", + "The requested share does not exist anymore" : "当前请求的共享已经不存在", + "Could not find category \"%s\"" : "无法找到分类 \"%s\"", + "Sunday" : "星期日", + "Monday" : "星期一", + "Tuesday" : "星期二", + "Wednesday" : "星期三", + "Thursday" : "星期四", + "Friday" : "星期五", + "Saturday" : "星期六", + "Sun." : "周日", + "Mon." : "周一", + "Tue." : "周二", + "Wed." : "周三", + "Thu." : "周四", + "Fri." : "周五", + "Sat." : "周六", + "Su" : "日", + "Mo" : "一", + "Tu" : "二", + "We" : "三", + "Th" : "四", + "Fr" : "五", + "Sa" : "六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Jan." : "一月", + "Feb." : "二月", + "Mar." : "三月", + "Apr." : "四月", + "May." : "五月", + "Jun." : "六月", + "Jul." : "七月", + "Aug." : "八月", + "Sep." : "九月", + "Oct." : "十月", + "Nov." : "十一月", + "Dec." : "十二月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”,“A-Z”,“0-9”和\"_.@-'\"", + "A valid username must be provided" : "必须提供合法的用户名", + "Username contains whitespace at the beginning or at the end" : "用户名在开头或结尾处包含空格", + "Username must not consist of dots only" : "用户名不能仅由点组成", + "Username is invalid because files already exist for this user" : "用户名无效,因为该用户已经存在文件", + "A valid password must be provided" : "必须提供合法的密码", + "The username is already being used" : "用户名已被使用", + "Could not create user" : "无法创建用户", + "User disabled" : "用户已禁用", + "Login canceled by app" : "已通过应用取消登录", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "应用 \"%1$s\" 无法安装,因为不能满足以下依赖: %2$s", + "a safe home for all your data" : "给您所有数据一个安全的家", + "File is currently busy, please try again later" : "文件当前正忙,请稍后再试", + "Can't read file" : "无法读取文件", + "Application is not enabled" : "应用程序未启用", + "Authentication error" : "认证出错", + "Token expired. Please reload page." : "Token 过期,请刷新页面。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "没有安装数据库驱动(SQLite、MySQL 或 PostgreSQL)。", + "Cannot write into \"config\" directory" : "无法写入“config”目录", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "这个通常可以通过赋予写入权限到 config 目录来修复。查看:%s", + "Cannot write into \"apps\" directory" : "无法写入“apps”目录", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "通常可以通过授予 Web 服务器对 apps 目录的写访问权限或在配置文件中禁用 appstore 来解决此问题。", + "Cannot create \"data\" directory" : "无法创建“data”目录 ", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "这个通常可以通过赋予根目录写入权限来修复。查看:%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "权限通常可以通过赋予根目录写入权限来修复。查看:%s。", + "Setting locale to %s failed" : "设置语言为 %s 失败", + "Please install one of these locales on your system and restart your webserver." : "请在您的系统中安装下述一种语言并重启 Web 服务器。", + "PHP module %s not installed." : "PHP %s 模块未安装。", + "Please ask your server administrator to install the module." : "请联系服务器管理员安装模块。", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 选项 \"%s\" 未设置为 \"%s\"。", + "Adjusting this setting in php.ini will make Nextcloud run again" : "在 php.ini 中调整该设置将导致 Nextcloud 重新运行", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 当前设置为 \"%s\",预期值为 \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "请在 php.ini 中设置 mbstring.func_overload0 以解决该问题", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "至少需要 libxml2 2.7.0. 当前安装 %s。", + "To fix this issue update your libxml2 version and restart your web server." : "升级您的 libxml2 版本然后重启 Web 服务器以解决该问题。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP 被设置为移除内联块,这将导致多个核心应用无法访问。", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "这可能由缓存/加速器导致的,例如 Zend OPcache 或 eAccelerator。", + "PHP modules have been installed, but they are still listed as missing?" : "PHP 模块已经安装,但仍然显示未安装?", + "Please ask your server administrator to restart the web server." : "请联系服务器管理员重启 Web 服务器。", + "PostgreSQL >= 9 required" : "要求 PostgreSQL >= 9", + "Please upgrade your database version" : "请升级您的数据库版本", + "Your data directory is readable by other users" : "您的数据目录可被其他用户读取", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "请更改权限为 0770 以避免其他用户查看目录。", + "Your data directory must be an absolute path" : "您的数据目录必须是绝对路径", + "Check the value of \"datadirectory\" in your configuration" : "请检查配置文件中 \"datadirectory\" 的值", + "Your data directory is invalid" : "您的数据目录无效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "请确定在根目录下有一个名为\".ocdata\"的文件。", + "Action \"%s\" not supported or implemented." : "操作 \"%s\" 不支持或未实现。", + "Authentication failed, wrong token or provider ID given" : "认证失败,提供了错误的token或提供者ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "缺少参数来完成请求。缺少的参数为:\"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" 已被云联合提供商 \"%2$s\" 使用", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ID为 \"%s\" 的云联合提供商不存在。", + "Could not obtain lock type %d on \"%s\"." : "无法在 \"%s\" 上获取锁类型 %d。", + "Storage unauthorized. %s" : "存储认证失败。%s", + "Storage incomplete configuration. %s" : "存储未完成配置。%s", + "Storage connection error. %s" : "存储连接错误。%s", + "Storage is temporarily not available" : "存储暂时不可用", + "Storage connection timeout. %s" : "存储连接超时。%s", + "Following databases are supported: %s" : "支持以下数据库:%s", + "Following platforms are supported: %s" : "支持以下平台:%s", + "Overview" : "概览", + "Basic settings" : "基本设置", + "Sharing" : "共享", + "Security" : "安全", + "Groupware" : "组件", + "Personal info" : "个人信息", + "Mobile & desktop" : "手机与电脑", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "这个通常可以通过赋予 apps 目录写入权限或者在 config 文件中关闭 AppStore 来修复。详情:%s" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_CN.json b/docker/overlays/nextcloud/html/lib/l10n/zh_CN.json new file mode 100644 index 0000000..7261d1d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_CN.json @@ -0,0 +1,236 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!", + "This can usually be fixed by giving the webserver write access to the config directory" : "您可以设置 Web 服务器对 config 目录的写权限修复这个问题", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "或者,如果希望保持 config.php 文件的只读权限,请将 \"config_is_read_only\" 设置为 true。", + "See %s" : "查看 %s", + "This can usually be fixed by giving the webserver write access to the config directory." : "通常可以通过授予 Web 服务器对 config 目录的写访问权限来解决此问题。", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "或者,如果希望保持 config.php 文件的只读权限,请将 \"config_is_read_only\" 设置为 true。查看 %s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "应用%1$s的文件替换不正确。请确认版本与当前服务器兼容。", + "Sample configuration detected" : "示例配置检测", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。", + "%1$s and %2$s" : "%1$s 和 %2$s", + "%1$s, %2$s and %3$s" : "%1$s,%2$s 和 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s,%2$s,%3$s 和 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s,%2$s,%3$s,%4$s 和 %5$s", + "Education Edition" : "教育版", + "Enterprise bundle" : "企业捆绑包", + "Groupware bundle" : "群组捆绑包", + "Hub bundle" : "枢纽捆绑包", + "Social sharing bundle" : "社交共享捆绑包", + "PHP %s or higher is required." : "要求 PHP 版本 %s 或者更高。", + "PHP with a version lower than %s is required." : "需要版本低于 %s 的PHP。", + "%sbit or higher PHP required." : "需要 %s 或更高版本的 PHP。", + "The following databases are supported: %s" : "支持以下数据库:%s", + "The command line tool %s could not be found" : "命令行工具 %s 未找到", + "The library %s is not available." : "库文件 %s 不可用", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "库版本 %1$s 高于需要的版本 %2$s - 可用版本 %3$s。", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "库版本 %1$s 低于需要的版本 %2$s - 可用版本 %3$s。", + "The following platforms are supported: %s" : "支持以下平台:%s", + "Server version %s or higher is required." : "需要服务器版本 %s 或更高版本。", + "Server version %s or lower is required." : "需要服务器版本 %s 或更低版本。", + "Logged in user must be an admin or sub admin" : "当前登录用户必须为管理员或子管理员", + "Logged in user must be an admin" : "当前登录用户必须为管理员", + "Wiping of device %s has started" : "设备%s的擦除操作已开始", + "Wiping of device »%s« has started" : "设备 »%s« 的擦除操作已开始", + "»%s« started remote wipe" : "»%s« 已开始远程擦除", + "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "设备或应用程序 »%s« 已开始远程擦除进程。进程完成后您将收到另一封邮件。", + "Wiping of device %s has finished" : "设备 %s 的擦除已完成", + "Wiping of device »%s« has finished" : "设备 »%s« 的擦除操作已完成", + "»%s« finished remote wipe" : "»%s« 已完成远程擦除", + "Device or application »%s« has finished the remote wipe process." : "设备或应用程序 »%s« 已完成远程擦除进程。", + "Remote wipe started" : "远程擦除开始", + "A remote wipe was started on device %s" : "设备%s上一个远程擦除操作已开始", + "Remote wipe finished" : "远程擦除完成", + "The remote wipe on %s has finished" : "%s上的远程擦除操作已完成", + "Authentication" : "验证", + "Unknown filetype" : "未知的文件类型", + "Invalid image" : "无效的图像", + "Avatar image is not square" : "头像图像不是正方形", + "today" : "今天", + "tomorrow" : "明天", + "yesterday" : "昨天", + "_in %n day_::_in %n days_" : ["%n天内"], + "_%n day ago_::_%n days ago_" : ["%n 天前"], + "next month" : "下月", + "last month" : "上月", + "_in %n month_::_in %n months_" : ["%n月内"], + "_%n month ago_::_%n months ago_" : ["%n 月前"], + "next year" : "明年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n年内"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n小时内"], + "_%n hour ago_::_%n hours ago_" : ["%n 小时前"], + "_in %n minute_::_in %n minutes_" : ["%n分钟内"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分钟前"], + "in a few seconds" : "几秒钟内", + "seconds ago" : "几秒前", + "Empty file" : "空文件", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "模块:%s不存在。请在 App 设置中开启或联系管理员。", + "File name is a reserved word" : "文件名包含敏感字符", + "File name contains at least one invalid character" : "文件名中存在至少一个非法字符", + "File name is too long" : "文件名过长", + "Dot files are not allowed" : "以 . 开头的文件不被允许", + "Empty filename is not allowed" : "不允许使用空名称", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "无法安装应用\"%s\",因为无法读取appinfo文件。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "应用程式 \"%s\" 无法安装,因为它与这个版本的服务器不兼容。", + "__language_name__" : "简体中文", + "This is an automatically sent email, please do not reply." : "这是一个自动生成的电子邮件,请不要回复。", + "Help" : "帮助", + "Apps" : "应用", + "Settings" : "设置", + "Log out" : "登出", + "Users" : "用户", + "Unknown user" : "未知用户", + "Additional settings" : "其他设置", + "%s enter the database username and name." : "%s 输入数据库用户名和名称。", + "%s enter the database username." : "%s 输入数据库用户名。", + "%s enter the database name." : "%s 输入数据库名称。", + "%s you may not use dots in the database name" : "%s 您不能在数据库名称中使用英文句号。", + "MySQL username and/or password not valid" : "MySQL数据库用户名和/或密码无效", + "You need to enter details of an existing account." : "您需要输入现有账号的详细信息。", + "Oracle connection could not be established" : "不能建立甲骨文连接", + "Oracle username and/or password not valid" : "Oracle 数据库用户名和/或密码无效", + "PostgreSQL username and/or password not valid" : "PostgreSQL 数据库用户名和/或密码无效", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X 不被支持并且 %s 在这个平台上无法正常工作。请自行承担风险!", + "For the best results, please consider using a GNU/Linux server instead." : "为了达到最好的效果,请考虑使用 GNU/Linux 服务器。", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "看起来这个 %s 实例运行在32位PHP环境中并且已在php.ini中配置open_basedir。这将在文件超过4GB时出现问题,我们极力反对这样做。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "请删除php.ini中的open_basedir设置或切换到64位PHP。", + "Set an admin username." : "请设置一个管理员用户名。", + "Set an admin password." : "请设置一个管理员密码。", + "Can't create or write into the data directory %s" : "无法创建或写入数据目录 %s", + "Invalid Federated Cloud ID" : "无效的联合云ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "共享后端 %s 必须实现 OCP\\Share_Backend 接口", + "Sharing backend %s not found" : "%s 的共享后端未找到", + "Sharing backend for %s not found" : "%s 的共享后端未找到", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s 与您共享了 »%2$s« 并希望添加:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s 与您共享了 »%2$s« 并希望添加", + "»%s« added a note to a file shared with you" : "»%s« 在与您共享的文件中添加了备注", + "Open »%s«" : "打开 »%s«", + "%1$s via %2$s" : "%1$s 通过 %2$s", + "You are not allowed to share %s" : "您无权共享 %s", + "Can’t increase permissions of %s" : "无法增加%s的权限。", + "Files can’t be shared with delete permissions" : "无法共享有删除权限的文件", + "Files can’t be shared with create permissions" : "无法共享有创建权限的文件", + "Expiration date is in the past" : "到期日期已过", + "Can’t set expiration date more than %s days in the future" : "无法将过期日期设置为超过 %s 天。", + "%1$s shared »%2$s« with you" : "%1$s 对您共享了 »%2$s«", + "%1$s shared »%2$s« with you." : "%1$s 对您共享了 »%2$s«。", + "Click the button below to open it." : "点击下方按钮可打开它。", + "The requested share does not exist anymore" : "当前请求的共享已经不存在", + "Could not find category \"%s\"" : "无法找到分类 \"%s\"", + "Sunday" : "星期日", + "Monday" : "星期一", + "Tuesday" : "星期二", + "Wednesday" : "星期三", + "Thursday" : "星期四", + "Friday" : "星期五", + "Saturday" : "星期六", + "Sun." : "周日", + "Mon." : "周一", + "Tue." : "周二", + "Wed." : "周三", + "Thu." : "周四", + "Fri." : "周五", + "Sat." : "周六", + "Su" : "日", + "Mo" : "一", + "Tu" : "二", + "We" : "三", + "Th" : "四", + "Fr" : "五", + "Sa" : "六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Jan." : "一月", + "Feb." : "二月", + "Mar." : "三月", + "Apr." : "四月", + "May." : "五月", + "Jun." : "六月", + "Jul." : "七月", + "Aug." : "八月", + "Sep." : "九月", + "Oct." : "十月", + "Nov." : "十一月", + "Dec." : "十二月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”,“A-Z”,“0-9”和\"_.@-'\"", + "A valid username must be provided" : "必须提供合法的用户名", + "Username contains whitespace at the beginning or at the end" : "用户名在开头或结尾处包含空格", + "Username must not consist of dots only" : "用户名不能仅由点组成", + "Username is invalid because files already exist for this user" : "用户名无效,因为该用户已经存在文件", + "A valid password must be provided" : "必须提供合法的密码", + "The username is already being used" : "用户名已被使用", + "Could not create user" : "无法创建用户", + "User disabled" : "用户已禁用", + "Login canceled by app" : "已通过应用取消登录", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "应用 \"%1$s\" 无法安装,因为不能满足以下依赖: %2$s", + "a safe home for all your data" : "给您所有数据一个安全的家", + "File is currently busy, please try again later" : "文件当前正忙,请稍后再试", + "Can't read file" : "无法读取文件", + "Application is not enabled" : "应用程序未启用", + "Authentication error" : "认证出错", + "Token expired. Please reload page." : "Token 过期,请刷新页面。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "没有安装数据库驱动(SQLite、MySQL 或 PostgreSQL)。", + "Cannot write into \"config\" directory" : "无法写入“config”目录", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "这个通常可以通过赋予写入权限到 config 目录来修复。查看:%s", + "Cannot write into \"apps\" directory" : "无法写入“apps”目录", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file." : "通常可以通过授予 Web 服务器对 apps 目录的写访问权限或在配置文件中禁用 appstore 来解决此问题。", + "Cannot create \"data\" directory" : "无法创建“data”目录 ", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "这个通常可以通过赋予根目录写入权限来修复。查看:%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "权限通常可以通过赋予根目录写入权限来修复。查看:%s。", + "Setting locale to %s failed" : "设置语言为 %s 失败", + "Please install one of these locales on your system and restart your webserver." : "请在您的系统中安装下述一种语言并重启 Web 服务器。", + "PHP module %s not installed." : "PHP %s 模块未安装。", + "Please ask your server administrator to install the module." : "请联系服务器管理员安装模块。", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 选项 \"%s\" 未设置为 \"%s\"。", + "Adjusting this setting in php.ini will make Nextcloud run again" : "在 php.ini 中调整该设置将导致 Nextcloud 重新运行", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 当前设置为 \"%s\",预期值为 \"0\"", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "请在 php.ini 中设置 mbstring.func_overload0 以解决该问题", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "至少需要 libxml2 2.7.0. 当前安装 %s。", + "To fix this issue update your libxml2 version and restart your web server." : "升级您的 libxml2 版本然后重启 Web 服务器以解决该问题。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP 被设置为移除内联块,这将导致多个核心应用无法访问。", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "这可能由缓存/加速器导致的,例如 Zend OPcache 或 eAccelerator。", + "PHP modules have been installed, but they are still listed as missing?" : "PHP 模块已经安装,但仍然显示未安装?", + "Please ask your server administrator to restart the web server." : "请联系服务器管理员重启 Web 服务器。", + "PostgreSQL >= 9 required" : "要求 PostgreSQL >= 9", + "Please upgrade your database version" : "请升级您的数据库版本", + "Your data directory is readable by other users" : "您的数据目录可被其他用户读取", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "请更改权限为 0770 以避免其他用户查看目录。", + "Your data directory must be an absolute path" : "您的数据目录必须是绝对路径", + "Check the value of \"datadirectory\" in your configuration" : "请检查配置文件中 \"datadirectory\" 的值", + "Your data directory is invalid" : "您的数据目录无效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "请确定在根目录下有一个名为\".ocdata\"的文件。", + "Action \"%s\" not supported or implemented." : "操作 \"%s\" 不支持或未实现。", + "Authentication failed, wrong token or provider ID given" : "认证失败,提供了错误的token或提供者ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "缺少参数来完成请求。缺少的参数为:\"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" 已被云联合提供商 \"%2$s\" 使用", + "Cloud Federation Provider with ID: \"%s\" does not exist." : "ID为 \"%s\" 的云联合提供商不存在。", + "Could not obtain lock type %d on \"%s\"." : "无法在 \"%s\" 上获取锁类型 %d。", + "Storage unauthorized. %s" : "存储认证失败。%s", + "Storage incomplete configuration. %s" : "存储未完成配置。%s", + "Storage connection error. %s" : "存储连接错误。%s", + "Storage is temporarily not available" : "存储暂时不可用", + "Storage connection timeout. %s" : "存储连接超时。%s", + "Following databases are supported: %s" : "支持以下数据库:%s", + "Following platforms are supported: %s" : "支持以下平台:%s", + "Overview" : "概览", + "Basic settings" : "基本设置", + "Sharing" : "共享", + "Security" : "安全", + "Groupware" : "组件", + "Personal info" : "个人信息", + "Mobile & desktop" : "手机与电脑", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "这个通常可以通过赋予 apps 目录写入权限或者在 config 文件中关闭 AppStore 来修复。详情:%s" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_HK.js b/docker/overlays/nextcloud/html/lib/l10n/zh_HK.js new file mode 100644 index 0000000..b70a038 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_HK.js @@ -0,0 +1,39 @@ +OC.L10N.register( + "lib", + { + "today" : "今日", + "yesterday" : "昨日", + "last month" : "前一月", + "_%n month ago_::_%n months ago_" : ["%n 月前"], + "last year" : "上年", + "_%n hour ago_::_%n hours ago_" : ["%n 小時前"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分鐘前"], + "seconds ago" : "秒前", + "__language_name__" : "繁體中文(香港)", + "Help" : "幫助", + "Apps" : "軟件", + "Settings" : "設定", + "Log out" : "登出", + "Users" : "用戶", + "Sunday" : "星期日", + "Monday" : "星期一", + "Tuesday" : "星期二", + "Wednesday" : "星期三", + "Thursday" : "星期四", + "Friday" : "星期五", + "Saturday" : "星期六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Sharing" : "分享" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_HK.json b/docker/overlays/nextcloud/html/lib/l10n/zh_HK.json new file mode 100644 index 0000000..a927b65 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_HK.json @@ -0,0 +1,37 @@ +{ "translations": { + "today" : "今日", + "yesterday" : "昨日", + "last month" : "前一月", + "_%n month ago_::_%n months ago_" : ["%n 月前"], + "last year" : "上年", + "_%n hour ago_::_%n hours ago_" : ["%n 小時前"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分鐘前"], + "seconds ago" : "秒前", + "__language_name__" : "繁體中文(香港)", + "Help" : "幫助", + "Apps" : "軟件", + "Settings" : "設定", + "Log out" : "登出", + "Users" : "用戶", + "Sunday" : "星期日", + "Monday" : "星期一", + "Tuesday" : "星期二", + "Wednesday" : "星期三", + "Thursday" : "星期四", + "Friday" : "星期五", + "Saturday" : "星期六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Sharing" : "分享" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_TW.js b/docker/overlays/nextcloud/html/lib/l10n/zh_TW.js new file mode 100644 index 0000000..3cc170e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_TW.js @@ -0,0 +1,221 @@ +OC.L10N.register( + "lib", + { + "Cannot write into \"config\" directory!" : "無法寫入 \"config\" 目錄!", + "This can usually be fixed by giving the webserver write access to the config directory" : "允許網頁伺服器寫入 \"config\" 目錄通常可以解決這個問題", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "或者,如果您比較希望保留 config.php 的唯讀狀態,請在該設定檔中將 \"config_is_read_only\" 設定為 true。", + "See %s" : "見 %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "或者,如果您比較希望保留 config.php 的唯讀狀態,請在該設定檔中將 \"config_is_read_only\" 設定為 true。見%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", + "Sample configuration detected" : "您目前正在使用範例設定", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%1$s and %2$s" : "%1$s 和 %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s、%2$s、%3$s 和 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s、%2$s、%3$s、%4$s 和 %5$s", + "Education Edition" : "教育版", + "Enterprise bundle" : "企業組合包", + "Groupware bundle" : "協作組合包", + "Social sharing bundle" : "社交網絡組合包", + "PHP %s or higher is required." : "需要 PHP %s 或更高版本", + "PHP with a version lower than %s is required." : "需要 PHP 版本低於 %s ", + "%sbit or higher PHP required." : "%s 或需要更高階版本的php", + "The command line tool %s could not be found" : "找不到命令列工具指令 %s", + "The library %s is not available." : "套件庫 %s 無法使用", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "需要使用 %2$s 版以上的 %1$s 函式庫,目前可用的版本是 %3$s", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "需要使用 %2$s 版以下的 %1$s 函式庫,目前可用的版本是 %3$s", + "Server version %s or higher is required." : "需要伺服器版本 %s 或更高", + "Server version %s or lower is required." : "需要伺服器版本 %s 或更低", + "Logged in user must be an admin or sub admin" : "登入的使用者必須要是管理員或是子管理員", + "Logged in user must be an admin" : "登入的使用者必須有管理員權限", + "Remote wipe started" : "遠端抹除已開始", + "A remote wipe was started on device %s" : "遠端抹除已經在裝置 %s 開始", + "Remote wipe finished" : "遠端抹除已完成", + "The remote wipe on %s has finished" : "%s 的遠端抹除已經完成", + "Authentication" : "驗證", + "Unknown filetype" : "未知的檔案類型", + "Invalid image" : "無效的圖片", + "Avatar image is not square" : "頭像不是正方形", + "today" : "今天", + "tomorrow" : "明天", + "yesterday" : "昨天", + "_in %n day_::_in %n days_" : ["在 %n 天內"], + "_%n day ago_::_%n days ago_" : ["%n 天前"], + "next month" : "下個月", + "last month" : "上個月", + "_in %n month_::_in %n months_" : ["在 %n 月內"], + "_%n month ago_::_%n months ago_" : ["%n 個月前"], + "next year" : "明年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n 年後"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n 小時後"], + "_%n hour ago_::_%n hours ago_" : ["%n 小時前"], + "_in %n minute_::_in %n minutes_" : ["%n 分鐘後"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分鐘前"], + "in a few seconds" : "幾秒後", + "seconds ago" : "幾秒前", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "名為 %s 的模組不存在,請在應用程式設定中啟用,或是聯絡系統管理員", + "File name is a reserved word" : "檔案名稱是保留字", + "File name contains at least one invalid character" : "檔案名稱含有不允許的字元", + "File name is too long" : "檔案名稱太長", + "Dot files are not allowed" : "不允許小數點開頭的檔案", + "Empty filename is not allowed" : "不允許空白的檔名", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "應用程式 \"%s\" 無法安裝,因為無法讀取 appinfo 檔案。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前版本的伺服器。", + "__language_name__" : "正體中文(臺灣)", + "This is an automatically sent email, please do not reply." : "此為自動寄送的電子郵件,請不要回覆。", + "Help" : "說明", + "Apps" : "應用程式", + "Settings" : "設定", + "Log out" : "登出", + "Users" : "使用者", + "Unknown user" : "未知的使用者", + "Additional settings" : "其他設定", + "%s enter the database username and name." : "%s 輸入資料庫名稱及使用者名稱", + "%s enter the database username." : "%s 輸入資料庫使用者名稱", + "%s enter the database name." : "%s 輸入資料庫名稱", + "%s you may not use dots in the database name" : "%s 資料庫名稱不能包含小數點", + "You need to enter details of an existing account." : "您必須輸入現有帳號的資訊", + "Oracle connection could not be established" : "無法建立 Oracle 資料庫連線", + "Oracle username and/or password not valid" : "Oracle 用戶名和/或密碼無效", + "PostgreSQL username and/or password not valid" : "PostgreSQL 用戶名和/或密碼無效", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "不支援 Mac OS X 而且 %s 在這個平台上面無法正常運作,請自行衡量風險後使用!", + "For the best results, please consider using a GNU/Linux server instead." : "請考慮使用 GNU/Linux 伺服器以獲得最佳體驗", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "看起來 %s 是在 32 位元的 PHP 環境運行,並且 php.ini 中被設置了 open_basedir 參數,這將讓超過 4GB 的檔案操作發生問題,強烈建議您更改設定。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "請移除 php.ini 中的 open_basedir 設定,或是改用 64 位元的 PHP", + "Set an admin username." : "設定管理員帳號", + "Set an admin password." : "設定管理員密碼", + "Can't create or write into the data directory %s" : "無法建立或寫入資料目錄 %s", + "Invalid Federated Cloud ID" : "無效的雲端聯邦 ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "分享後端 %s 必須實作 OCP\\Share_Backend 界面", + "Sharing backend %s not found" : "找不到分享後端 %s", + "Sharing backend for %s not found" : "找不到 %s 的分享後端", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s 與您分享了 %2$s ,且想要加入:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s 與您分享了 %2$s ,且想要加入", + "»%s« added a note to a file shared with you" : "%s 在與您分享的檔案中加入了註解", + "Open »%s«" : "開啟 »%s«", + "%1$s via %2$s" : "%1$s 由 %2$s", + "You are not allowed to share %s" : "你不被允許分享 %s", + "Can’t increase permissions of %s" : "無法增加 %s 的權限", + "Files can’t be shared with delete permissions" : "無法分享具有刪除權限的檔案", + "Files can’t be shared with create permissions" : "無法分享具有新建權限的檔案", + "Expiration date is in the past" : "到期日為過去的日期", + "Can’t set expiration date more than %s days in the future" : "到期日不能設定為 %s 天以後的日期", + "%1$s shared »%2$s« with you" : "%1$s 與您分享了 %2$s", + "%1$s shared »%2$s« with you." : "%1$s 與您分享了 %2$s", + "Click the button below to open it." : "點下方連結開啟", + "The requested share does not exist anymore" : "該分享已經不存在", + "Could not find category \"%s\"" : "找不到分類:\"%s\"", + "Sunday" : "週日", + "Monday" : "週一", + "Tuesday" : "週二", + "Wednesday" : "週三", + "Thursday" : "週四", + "Friday" : "週五", + "Saturday" : "週六", + "Sun." : "日", + "Mon." : "一", + "Tue." : "二", + "Wed." : "三", + "Thu." : "四", + "Fri." : "五", + "Sat." : "六", + "Su" : "日", + "Mo" : "一", + "Tu" : "二", + "We" : "三", + "Th" : "四", + "Fr" : "五", + "Sa" : "六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Jan." : "一月", + "Feb." : "二月", + "Mar." : "三月", + "Apr." : "四月", + "May." : "五月", + "Jun." : "六月", + "Jul." : "七月", + "Aug." : "八月", + "Sep." : "九月", + "Oct." : "十月", + "Nov." : "十一月", + "Dec." : "十二月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "使用者名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"", + "A valid username must be provided" : "必須提供一個有效的用戶名", + "Username contains whitespace at the beginning or at the end" : "用戶名的開頭或結尾有空白", + "Username must not consist of dots only" : "使用者名稱不能只包含小數點", + "A valid password must be provided" : "一定要提供一個有效的密碼", + "The username is already being used" : "這個使用者名稱已經有人使用了", + "Could not create user" : "無法建立使用者", + "User disabled" : "使用者已停用", + "Login canceled by app" : "應用程式取消了登入", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "應用程式 \"%1$s\" 無法被安裝,缺少下列所需元件: %2$s", + "a safe home for all your data" : "您資料的安全屋", + "File is currently busy, please try again later" : "檔案目前忙碌中,請稍候再試", + "Can't read file" : "無法讀取檔案", + "Application is not enabled" : "應用程式未啟用", + "Authentication error" : "認證錯誤", + "Token expired. Please reload page." : "Token 過期,請重新整理頁面。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "沒有安裝資料庫驅動程式 (sqlite, mysql, 或 postgresql)", + "Cannot write into \"config\" directory" : "無法寫入 config 目錄", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "允許網頁伺服器寫入 \"config\" 目錄通常可以解決這個問題,詳見 %s", + "Cannot write into \"apps\" directory" : "無法寫入 apps 目錄", + "Cannot create \"data\" directory" : "無法建立 \"data\" 目錄", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "開放網頁伺服器存取根目錄通常就可以修正這個問題,詳見 %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "開放網頁伺服器存取根目錄通常就可以修正權限問題,詳見 %s", + "Setting locale to %s failed" : "設定語系為 %s 失敗", + "Please install one of these locales on your system and restart your webserver." : "請在系統中安裝這些語系的其中一個,然後重啓網頁伺服器", + "PHP module %s not installed." : "未安裝 PHP 模組 %s", + "Please ask your server administrator to install the module." : "請詢問系統管理員來安裝這些模組", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 設定值 \"%s\" 沒有被設定為 \"%s\"", + "Adjusting this setting in php.ini will make Nextcloud run again" : "調整 php.ini 中的設定,使 Nextcloud 重新運作", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 應該要被設定成 \"0\" 而不是目前的設定 \"%s\" ", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "為了修正這個問題,請到 php.ini 將 mbstring.func_overload 的值改為 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s 。", + "To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP 已經設定成「剪除 inline doc block」模式,這將會使幾個核心應用程式無法使用", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "這大概是由快取或是加速器像是 Zend OPcache, eAccelerator 造成的", + "PHP modules have been installed, but they are still listed as missing?" : "你已經安裝了指定的 PHP 模組,可是還是顯示為找不到嗎?", + "Please ask your server administrator to restart the web server." : "請聯絡您的系統管理員重新啟動網頁伺服器", + "PostgreSQL >= 9 required" : "需要 PostgreSQL 版本 >= 9", + "Please upgrade your database version" : "請升級您的資料庫版本", + "Your data directory is readable by other users" : "您的資料目錄可以被其他使用者讀取", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "請將該目錄權限設定為 0770 ,以免其他使用者讀取目錄列表", + "Your data directory must be an absolute path" : "您的資料目錄必須為絕對路徑", + "Check the value of \"datadirectory\" in your configuration" : "請檢查您的設定檔中 \"datadirectory\" 的值", + "Your data directory is invalid" : "您的資料目錄無效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "請確保資料目錄最上層有一個 \".ocdata\" 檔案", + "Action \"%s\" not supported or implemented." : "操作 \"%s\" 並未支援,或是尚未實作", + "Authentication failed, wrong token or provider ID given" : "認證失敗,提供了錯誤的 token 或是 provider ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "欠缺完成請求所需的參數: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" 已經被另一個雲端聯盟供應者 \"%2$s\" 所使用", + "Could not obtain lock type %d on \"%s\"." : "無法取得鎖定:類型 %d ,檔案 %s", + "Storage unauthorized. %s" : "儲存空間未經授權。%s", + "Storage incomplete configuration. %s" : "儲存空間配置尚未完成。%s", + "Storage connection error. %s" : "儲存空間連線錯誤。%s", + "Storage is temporarily not available" : "儲存空間暫時無法使用", + "Storage connection timeout. %s" : "儲存空間連線逾時。%s", + "Following databases are supported: %s" : "支援下列資料庫: %s", + "Following platforms are supported: %s" : "支援下列平台: %s", + "Overview" : "概觀", + "Basic settings" : "基本設定", + "Sharing" : "分享", + "Security" : "安全性", + "Groupware" : "協作應用程式", + "Personal info" : "個人資訊", + "Mobile & desktop" : "行動裝置及桌面", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "開放網頁伺服器存取 apps 目錄,或是在設定檔中關閉 appstore 功能通常就可以修正這個問題,詳見 %s" +}, +"nplurals=1; plural=0;"); diff --git a/docker/overlays/nextcloud/html/lib/l10n/zh_TW.json b/docker/overlays/nextcloud/html/lib/l10n/zh_TW.json new file mode 100644 index 0000000..65dc585 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/l10n/zh_TW.json @@ -0,0 +1,219 @@ +{ "translations": { + "Cannot write into \"config\" directory!" : "無法寫入 \"config\" 目錄!", + "This can usually be fixed by giving the webserver write access to the config directory" : "允許網頁伺服器寫入 \"config\" 目錄通常可以解決這個問題", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "或者,如果您比較希望保留 config.php 的唯讀狀態,請在該設定檔中將 \"config_is_read_only\" 設定為 true。", + "See %s" : "見 %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "或者,如果您比較希望保留 config.php 的唯讀狀態,請在該設定檔中將 \"config_is_read_only\" 設定為 true。見%s", + "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", + "Sample configuration detected" : "您目前正在使用範例設定", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%1$s and %2$s" : "%1$s 和 %2$s", + "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", + "%1$s, %2$s, %3$s and %4$s" : "%1$s、%2$s、%3$s 和 %4$s", + "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s、%2$s、%3$s、%4$s 和 %5$s", + "Education Edition" : "教育版", + "Enterprise bundle" : "企業組合包", + "Groupware bundle" : "協作組合包", + "Social sharing bundle" : "社交網絡組合包", + "PHP %s or higher is required." : "需要 PHP %s 或更高版本", + "PHP with a version lower than %s is required." : "需要 PHP 版本低於 %s ", + "%sbit or higher PHP required." : "%s 或需要更高階版本的php", + "The command line tool %s could not be found" : "找不到命令列工具指令 %s", + "The library %s is not available." : "套件庫 %s 無法使用", + "Library %1$s with a version higher than %2$s is required - available version %3$s." : "需要使用 %2$s 版以上的 %1$s 函式庫,目前可用的版本是 %3$s", + "Library %1$s with a version lower than %2$s is required - available version %3$s." : "需要使用 %2$s 版以下的 %1$s 函式庫,目前可用的版本是 %3$s", + "Server version %s or higher is required." : "需要伺服器版本 %s 或更高", + "Server version %s or lower is required." : "需要伺服器版本 %s 或更低", + "Logged in user must be an admin or sub admin" : "登入的使用者必須要是管理員或是子管理員", + "Logged in user must be an admin" : "登入的使用者必須有管理員權限", + "Remote wipe started" : "遠端抹除已開始", + "A remote wipe was started on device %s" : "遠端抹除已經在裝置 %s 開始", + "Remote wipe finished" : "遠端抹除已完成", + "The remote wipe on %s has finished" : "%s 的遠端抹除已經完成", + "Authentication" : "驗證", + "Unknown filetype" : "未知的檔案類型", + "Invalid image" : "無效的圖片", + "Avatar image is not square" : "頭像不是正方形", + "today" : "今天", + "tomorrow" : "明天", + "yesterday" : "昨天", + "_in %n day_::_in %n days_" : ["在 %n 天內"], + "_%n day ago_::_%n days ago_" : ["%n 天前"], + "next month" : "下個月", + "last month" : "上個月", + "_in %n month_::_in %n months_" : ["在 %n 月內"], + "_%n month ago_::_%n months ago_" : ["%n 個月前"], + "next year" : "明年", + "last year" : "去年", + "_in %n year_::_in %n years_" : ["%n 年後"], + "_%n year ago_::_%n years ago_" : ["%n 年前"], + "_in %n hour_::_in %n hours_" : ["%n 小時後"], + "_%n hour ago_::_%n hours ago_" : ["%n 小時前"], + "_in %n minute_::_in %n minutes_" : ["%n 分鐘後"], + "_%n minute ago_::_%n minutes ago_" : ["%n 分鐘前"], + "in a few seconds" : "幾秒後", + "seconds ago" : "幾秒前", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "名為 %s 的模組不存在,請在應用程式設定中啟用,或是聯絡系統管理員", + "File name is a reserved word" : "檔案名稱是保留字", + "File name contains at least one invalid character" : "檔案名稱含有不允許的字元", + "File name is too long" : "檔案名稱太長", + "Dot files are not allowed" : "不允許小數點開頭的檔案", + "Empty filename is not allowed" : "不允許空白的檔名", + "App \"%s\" cannot be installed because appinfo file cannot be read." : "應用程式 \"%s\" 無法安裝,因為無法讀取 appinfo 檔案。", + "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前版本的伺服器。", + "__language_name__" : "正體中文(臺灣)", + "This is an automatically sent email, please do not reply." : "此為自動寄送的電子郵件,請不要回覆。", + "Help" : "說明", + "Apps" : "應用程式", + "Settings" : "設定", + "Log out" : "登出", + "Users" : "使用者", + "Unknown user" : "未知的使用者", + "Additional settings" : "其他設定", + "%s enter the database username and name." : "%s 輸入資料庫名稱及使用者名稱", + "%s enter the database username." : "%s 輸入資料庫使用者名稱", + "%s enter the database name." : "%s 輸入資料庫名稱", + "%s you may not use dots in the database name" : "%s 資料庫名稱不能包含小數點", + "You need to enter details of an existing account." : "您必須輸入現有帳號的資訊", + "Oracle connection could not be established" : "無法建立 Oracle 資料庫連線", + "Oracle username and/or password not valid" : "Oracle 用戶名和/或密碼無效", + "PostgreSQL username and/or password not valid" : "PostgreSQL 用戶名和/或密碼無效", + "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "不支援 Mac OS X 而且 %s 在這個平台上面無法正常運作,請自行衡量風險後使用!", + "For the best results, please consider using a GNU/Linux server instead." : "請考慮使用 GNU/Linux 伺服器以獲得最佳體驗", + "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "看起來 %s 是在 32 位元的 PHP 環境運行,並且 php.ini 中被設置了 open_basedir 參數,這將讓超過 4GB 的檔案操作發生問題,強烈建議您更改設定。", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "請移除 php.ini 中的 open_basedir 設定,或是改用 64 位元的 PHP", + "Set an admin username." : "設定管理員帳號", + "Set an admin password." : "設定管理員密碼", + "Can't create or write into the data directory %s" : "無法建立或寫入資料目錄 %s", + "Invalid Federated Cloud ID" : "無效的雲端聯邦 ID", + "Sharing backend %s must implement the interface OCP\\Share_Backend" : "分享後端 %s 必須實作 OCP\\Share_Backend 界面", + "Sharing backend %s not found" : "找不到分享後端 %s", + "Sharing backend for %s not found" : "找不到 %s 的分享後端", + "%1$s shared »%2$s« with you and wants to add:" : "%1$s 與您分享了 %2$s ,且想要加入:", + "%1$s shared »%2$s« with you and wants to add" : "%1$s 與您分享了 %2$s ,且想要加入", + "»%s« added a note to a file shared with you" : "%s 在與您分享的檔案中加入了註解", + "Open »%s«" : "開啟 »%s«", + "%1$s via %2$s" : "%1$s 由 %2$s", + "You are not allowed to share %s" : "你不被允許分享 %s", + "Can’t increase permissions of %s" : "無法增加 %s 的權限", + "Files can’t be shared with delete permissions" : "無法分享具有刪除權限的檔案", + "Files can’t be shared with create permissions" : "無法分享具有新建權限的檔案", + "Expiration date is in the past" : "到期日為過去的日期", + "Can’t set expiration date more than %s days in the future" : "到期日不能設定為 %s 天以後的日期", + "%1$s shared »%2$s« with you" : "%1$s 與您分享了 %2$s", + "%1$s shared »%2$s« with you." : "%1$s 與您分享了 %2$s", + "Click the button below to open it." : "點下方連結開啟", + "The requested share does not exist anymore" : "該分享已經不存在", + "Could not find category \"%s\"" : "找不到分類:\"%s\"", + "Sunday" : "週日", + "Monday" : "週一", + "Tuesday" : "週二", + "Wednesday" : "週三", + "Thursday" : "週四", + "Friday" : "週五", + "Saturday" : "週六", + "Sun." : "日", + "Mon." : "一", + "Tue." : "二", + "Wed." : "三", + "Thu." : "四", + "Fri." : "五", + "Sat." : "六", + "Su" : "日", + "Mo" : "一", + "Tu" : "二", + "We" : "三", + "Th" : "四", + "Fr" : "五", + "Sa" : "六", + "January" : "一月", + "February" : "二月", + "March" : "三月", + "April" : "四月", + "May" : "五月", + "June" : "六月", + "July" : "七月", + "August" : "八月", + "September" : "九月", + "October" : "十月", + "November" : "十一月", + "December" : "十二月", + "Jan." : "一月", + "Feb." : "二月", + "Mar." : "三月", + "Apr." : "四月", + "May." : "五月", + "Jun." : "六月", + "Jul." : "七月", + "Aug." : "八月", + "Sep." : "九月", + "Oct." : "十月", + "Nov." : "十一月", + "Dec." : "十二月", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "使用者名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"", + "A valid username must be provided" : "必須提供一個有效的用戶名", + "Username contains whitespace at the beginning or at the end" : "用戶名的開頭或結尾有空白", + "Username must not consist of dots only" : "使用者名稱不能只包含小數點", + "A valid password must be provided" : "一定要提供一個有效的密碼", + "The username is already being used" : "這個使用者名稱已經有人使用了", + "Could not create user" : "無法建立使用者", + "User disabled" : "使用者已停用", + "Login canceled by app" : "應用程式取消了登入", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "應用程式 \"%1$s\" 無法被安裝,缺少下列所需元件: %2$s", + "a safe home for all your data" : "您資料的安全屋", + "File is currently busy, please try again later" : "檔案目前忙碌中,請稍候再試", + "Can't read file" : "無法讀取檔案", + "Application is not enabled" : "應用程式未啟用", + "Authentication error" : "認證錯誤", + "Token expired. Please reload page." : "Token 過期,請重新整理頁面。", + "No database drivers (sqlite, mysql, or postgresql) installed." : "沒有安裝資料庫驅動程式 (sqlite, mysql, 或 postgresql)", + "Cannot write into \"config\" directory" : "無法寫入 config 目錄", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "允許網頁伺服器寫入 \"config\" 目錄通常可以解決這個問題,詳見 %s", + "Cannot write into \"apps\" directory" : "無法寫入 apps 目錄", + "Cannot create \"data\" directory" : "無法建立 \"data\" 目錄", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "開放網頁伺服器存取根目錄通常就可以修正這個問題,詳見 %s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "開放網頁伺服器存取根目錄通常就可以修正權限問題,詳見 %s", + "Setting locale to %s failed" : "設定語系為 %s 失敗", + "Please install one of these locales on your system and restart your webserver." : "請在系統中安裝這些語系的其中一個,然後重啓網頁伺服器", + "PHP module %s not installed." : "未安裝 PHP 模組 %s", + "Please ask your server administrator to install the module." : "請詢問系統管理員來安裝這些模組", + "PHP setting \"%s\" is not set to \"%s\"." : "PHP 設定值 \"%s\" 沒有被設定為 \"%s\"", + "Adjusting this setting in php.ini will make Nextcloud run again" : "調整 php.ini 中的設定,使 Nextcloud 重新運作", + "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload 應該要被設定成 \"0\" 而不是目前的設定 \"%s\" ", + "To fix this issue set mbstring.func_overload to 0 in your php.ini" : "為了修正這個問題,請到 php.ini 將 mbstring.func_overload 的值改為 0", + "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s 。", + "To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。", + "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP 已經設定成「剪除 inline doc block」模式,這將會使幾個核心應用程式無法使用", + "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "這大概是由快取或是加速器像是 Zend OPcache, eAccelerator 造成的", + "PHP modules have been installed, but they are still listed as missing?" : "你已經安裝了指定的 PHP 模組,可是還是顯示為找不到嗎?", + "Please ask your server administrator to restart the web server." : "請聯絡您的系統管理員重新啟動網頁伺服器", + "PostgreSQL >= 9 required" : "需要 PostgreSQL 版本 >= 9", + "Please upgrade your database version" : "請升級您的資料庫版本", + "Your data directory is readable by other users" : "您的資料目錄可以被其他使用者讀取", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "請將該目錄權限設定為 0770 ,以免其他使用者讀取目錄列表", + "Your data directory must be an absolute path" : "您的資料目錄必須為絕對路徑", + "Check the value of \"datadirectory\" in your configuration" : "請檢查您的設定檔中 \"datadirectory\" 的值", + "Your data directory is invalid" : "您的資料目錄無效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "請確保資料目錄最上層有一個 \".ocdata\" 檔案", + "Action \"%s\" not supported or implemented." : "操作 \"%s\" 並未支援,或是尚未實作", + "Authentication failed, wrong token or provider ID given" : "認證失敗,提供了錯誤的 token 或是 provider ID", + "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "欠缺完成請求所需的參數: \"%s\"", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" 已經被另一個雲端聯盟供應者 \"%2$s\" 所使用", + "Could not obtain lock type %d on \"%s\"." : "無法取得鎖定:類型 %d ,檔案 %s", + "Storage unauthorized. %s" : "儲存空間未經授權。%s", + "Storage incomplete configuration. %s" : "儲存空間配置尚未完成。%s", + "Storage connection error. %s" : "儲存空間連線錯誤。%s", + "Storage is temporarily not available" : "儲存空間暫時無法使用", + "Storage connection timeout. %s" : "儲存空間連線逾時。%s", + "Following databases are supported: %s" : "支援下列資料庫: %s", + "Following platforms are supported: %s" : "支援下列平台: %s", + "Overview" : "概觀", + "Basic settings" : "基本設定", + "Sharing" : "分享", + "Security" : "安全性", + "Groupware" : "協作應用程式", + "Personal info" : "個人資訊", + "Mobile & desktop" : "行動裝置及桌面", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "開放網頁伺服器存取 apps 目錄,或是在設定檔中關閉 appstore 功能通常就可以修正這個問題,詳見 %s" +},"pluralForm" :"nplurals=1; plural=0;" +} \ No newline at end of file diff --git a/docker/overlays/nextcloud/html/lib/private/Accounts/Account.php b/docker/overlays/nextcloud/html/lib/private/Accounts/Account.php new file mode 100644 index 0000000..e7aeb6f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Accounts/Account.php @@ -0,0 +1,82 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Accounts; + +use OCP\Accounts\IAccount; +use OCP\Accounts\IAccountProperty; +use OCP\Accounts\PropertyDoesNotExistException; +use OCP\IUser; + +class Account implements IAccount { + + /** @var IAccountProperty[] */ + private $properties = []; + + /** @var IUser */ + private $user; + + public function __construct(IUser $user) { + $this->user = $user; + } + + public function setProperty(string $property, string $value, string $scope, string $verified): IAccount { + $this->properties[$property] = new AccountProperty($property, $value, $scope, $verified); + return $this; + } + + public function getProperty(string $property): IAccountProperty { + if (!array_key_exists($property, $this->properties)) { + throw new PropertyDoesNotExistException($property); + } + return $this->properties[$property]; + } + + public function getProperties(): array { + return $this->properties; + } + + public function getFilteredProperties(string $scope = null, string $verified = null): array { + return \array_filter($this->properties, function (IAccountProperty $obj) use ($scope, $verified) { + if ($scope !== null && $scope !== $obj->getScope()) { + return false; + } + if ($verified !== null && $verified !== $obj->getVerified()) { + return false; + } + return true; + }); + } + + public function jsonSerialize() { + return $this->properties; + } + + public function getUser(): IUser { + return $this->user; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Accounts/AccountManager.php b/docker/overlays/nextcloud/html/lib/private/Accounts/AccountManager.php new file mode 100644 index 0000000..1f23e7e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Accounts/AccountManager.php @@ -0,0 +1,358 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Accounts; + +use OCA\Settings\BackgroundJobs\VerifyUserData; +use OCP\Accounts\IAccount; +use OCP\Accounts\IAccountManager; +use OCP\BackgroundJob\IJobList; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; +use function json_decode; +use function json_last_error; + +/** + * Class AccountManager + * + * Manage system accounts table + * + * @group DB + * @package OC\Accounts + */ +class AccountManager implements IAccountManager { + + /** @var IDBConnection database connection */ + private $connection; + + /** @var string table name */ + private $table = 'accounts'; + + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** @var IJobList */ + private $jobList; + + /** @var ILogger */ + private $logger; + + /** + * AccountManager constructor. + * + * @param IDBConnection $connection + * @param EventDispatcherInterface $eventDispatcher + * @param IJobList $jobList + */ + public function __construct(IDBConnection $connection, + EventDispatcherInterface $eventDispatcher, + IJobList $jobList, + ILogger $logger) { + $this->connection = $connection; + $this->eventDispatcher = $eventDispatcher; + $this->jobList = $jobList; + $this->logger = $logger; + } + + /** + * update user record + * + * @param IUser $user + * @param $data + */ + public function updateUser(IUser $user, $data) { + $userData = $this->getUser($user); + $updated = true; + if (empty($userData)) { + $this->insertNewUser($user, $data); + } elseif ($userData !== $data) { + $data = $this->checkEmailVerification($userData, $data, $user); + $data = $this->updateVerifyStatus($userData, $data); + $this->updateExistingUser($user, $data); + } else { + // nothing needs to be done if new and old data set are the same + $updated = false; + } + + if ($updated) { + $this->eventDispatcher->dispatch( + 'OC\AccountManager::userUpdated', + new GenericEvent($user, $data) + ); + } + } + + /** + * delete user from accounts table + * + * @param IUser $user + */ + public function deleteUser(IUser $user) { + $uid = $user->getUID(); + $query = $this->connection->getQueryBuilder(); + $query->delete($this->table) + ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) + ->execute(); + } + + /** + * get stored data from a given user + * + * @param IUser $user + * @return array + */ + public function getUser(IUser $user) { + $uid = $user->getUID(); + $query = $this->connection->getQueryBuilder(); + $query->select('data') + ->from($this->table) + ->where($query->expr()->eq('uid', $query->createParameter('uid'))) + ->setParameter('uid', $uid); + $result = $query->execute(); + $accountData = $result->fetchAll(); + $result->closeCursor(); + + if (empty($accountData)) { + $userData = $this->buildDefaultUserRecord($user); + $this->insertNewUser($user, $userData); + return $userData; + } + + $userDataArray = json_decode($accountData[0]['data'], true); + $jsonError = json_last_error(); + if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) { + $this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record"); + return $this->buildDefaultUserRecord($user); + } + + $userDataArray = $this->addMissingDefaultValues($userDataArray); + + return $userDataArray; + } + + /** + * check if we need to ask the server for email verification, if yes we create a cronjob + * + * @param $oldData + * @param $newData + * @param IUser $user + * @return array + */ + protected function checkEmailVerification($oldData, $newData, IUser $user) { + if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) { + $this->jobList->add(VerifyUserData::class, + [ + 'verificationCode' => '', + 'data' => $newData[self::PROPERTY_EMAIL]['value'], + 'type' => self::PROPERTY_EMAIL, + 'uid' => $user->getUID(), + 'try' => 0, + 'lastRun' => time() + ] + ); + $newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS; + } + + return $newData; + } + + /** + * make sure that all expected data are set + * + * @param array $userData + * @return array + */ + protected function addMissingDefaultValues(array $userData) { + foreach ($userData as $key => $value) { + if (!isset($userData[$key]['verified'])) { + $userData[$key]['verified'] = self::NOT_VERIFIED; + } + } + + return $userData; + } + + /** + * reset verification status if personal data changed + * + * @param array $oldData + * @param array $newData + * @return array + */ + protected function updateVerifyStatus($oldData, $newData) { + + // which account was already verified successfully? + $twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED; + $websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED; + $emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED; + + // keep old verification status if we don't have a new one + if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) { + // keep old verification status if value didn't changed and an old value exists + $keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']); + $newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED; + } + + if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) { + // keep old verification status if value didn't changed and an old value exists + $keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']); + $newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED; + } + + if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) { + // keep old verification status if value didn't changed and an old value exists + $keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']); + $newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS; + } + + // reset verification status if a value from a previously verified data was changed + if ($twitterVerified && + $oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value'] + ) { + $newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED; + } + + if ($websiteVerified && + $oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value'] + ) { + $newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED; + } + + if ($emailVerified && + $oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value'] + ) { + $newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED; + } + + return $newData; + } + + /** + * add new user to accounts table + * + * @param IUser $user + * @param array $data + */ + protected function insertNewUser(IUser $user, $data) { + $uid = $user->getUID(); + $jsonEncodedData = json_encode($data); + $query = $this->connection->getQueryBuilder(); + $query->insert($this->table) + ->values( + [ + 'uid' => $query->createNamedParameter($uid), + 'data' => $query->createNamedParameter($jsonEncodedData), + ] + ) + ->execute(); + } + + /** + * update existing user in accounts table + * + * @param IUser $user + * @param array $data + */ + protected function updateExistingUser(IUser $user, $data) { + $uid = $user->getUID(); + $jsonEncodedData = json_encode($data); + $query = $this->connection->getQueryBuilder(); + $query->update($this->table) + ->set('data', $query->createNamedParameter($jsonEncodedData)) + ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) + ->execute(); + } + + /** + * build default user record in case not data set exists yet + * + * @param IUser $user + * @return array + */ + protected function buildDefaultUserRecord(IUser $user) { + return [ + self::PROPERTY_DISPLAYNAME => + [ + 'value' => $user->getDisplayName(), + 'scope' => self::VISIBILITY_CONTACTS_ONLY, + 'verified' => self::NOT_VERIFIED, + ], + self::PROPERTY_ADDRESS => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + 'verified' => self::NOT_VERIFIED, + ], + self::PROPERTY_WEBSITE => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + 'verified' => self::NOT_VERIFIED, + ], + self::PROPERTY_EMAIL => + [ + 'value' => $user->getEMailAddress(), + 'scope' => self::VISIBILITY_CONTACTS_ONLY, + 'verified' => self::NOT_VERIFIED, + ], + self::PROPERTY_AVATAR => + [ + 'scope' => self::VISIBILITY_CONTACTS_ONLY + ], + self::PROPERTY_PHONE => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + 'verified' => self::NOT_VERIFIED, + ], + self::PROPERTY_TWITTER => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + 'verified' => self::NOT_VERIFIED, + ], + ]; + } + + private function parseAccountData(IUser $user, $data): Account { + $account = new Account($user); + foreach ($data as $property => $accountData) { + $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::VISIBILITY_PRIVATE, $accountData['verified'] ?? self::NOT_VERIFIED); + } + return $account; + } + + public function getAccount(IUser $user): IAccount { + return $this->parseAccountData($user, $this->getUser($user)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Accounts/AccountProperty.php b/docker/overlays/nextcloud/html/lib/private/Accounts/AccountProperty.php new file mode 100644 index 0000000..97f9b1c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Accounts/AccountProperty.php @@ -0,0 +1,140 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Accounts; + +use OCP\Accounts\IAccountProperty; + +class AccountProperty implements IAccountProperty { + + /** @var string */ + private $name; + /** @var string */ + private $value; + /** @var string */ + private $scope; + /** @var string */ + private $verified; + + public function __construct(string $name, string $value, string $scope, string $verified) { + $this->name = $name; + $this->value = $value; + $this->scope = $scope; + $this->verified = $verified; + } + + public function jsonSerialize() { + return [ + 'name' => $this->getName(), + 'value' => $this->getValue(), + 'scope' => $this->getScope(), + 'verified' => $this->getVerified() + ]; + } + + /** + * Set the value of a property + * + * @since 15.0.0 + * + * @param string $value + * @return IAccountProperty + */ + public function setValue(string $value): IAccountProperty { + $this->value = $value; + return $this; + } + + /** + * Set the scope of a property + * + * @since 15.0.0 + * + * @param string $scope + * @return IAccountProperty + */ + public function setScope(string $scope): IAccountProperty { + $this->scope = $scope; + return $this; + } + + /** + * Set the verification status of a property + * + * @since 15.0.0 + * + * @param string $verified + * @return IAccountProperty + */ + public function setVerified(string $verified): IAccountProperty { + $this->verified = $verified; + return $this; + } + + /** + * Get the name of a property + * + * @since 15.0.0 + * + * @return string + */ + public function getName(): string { + return $this->name; + } + + /** + * Get the value of a property + * + * @since 15.0.0 + * + * @return string + */ + public function getValue(): string { + return $this->value; + } + + /** + * Get the scope of a property + * + * @since 15.0.0 + * + * @return string + */ + public function getScope(): string { + return $this->scope; + } + + /** + * Get the verification status of a property + * + * @since 15.0.0 + * + * @return string + */ + public function getVerified(): string { + return $this->verified; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Accounts/Hooks.php b/docker/overlays/nextcloud/html/lib/private/Accounts/Hooks.php new file mode 100644 index 0000000..2288b88 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Accounts/Hooks.php @@ -0,0 +1,94 @@ + + * + * @author Bjoern Schiessle + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Accounts; + +use OCP\ILogger; +use OCP\IUser; + +class Hooks { + + /** @var AccountManager */ + private $accountManager = null; + + /** @var ILogger */ + private $logger; + + /** + * Hooks constructor. + * + * @param ILogger $logger + */ + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + /** + * update accounts table if email address or display name was changed from outside + * + * @param array $params + */ + public function changeUserHook($params) { + $accountManager = $this->getAccountManager(); + + /** @var IUser $user */ + $user = isset($params['user']) ? $params['user'] : null; + $feature = isset($params['feature']) ? $params['feature'] : null; + $newValue = isset($params['value']) ? $params['value'] : null; + + if (is_null($user) || is_null($feature) || is_null($newValue)) { + $this->logger->warning('Missing expected parameters in change user hook'); + return; + } + + $accountData = $accountManager->getUser($user); + + switch ($feature) { + case 'eMailAddress': + if ($accountData[AccountManager::PROPERTY_EMAIL]['value'] !== $newValue) { + $accountData[AccountManager::PROPERTY_EMAIL]['value'] = $newValue; + $accountManager->updateUser($user, $accountData); + } + break; + case 'displayName': + if ($accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] !== $newValue) { + $accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $newValue; + $accountManager->updateUser($user, $accountData); + } + break; + } + } + + /** + * return instance of accountManager + * + * @return AccountManager + */ + protected function getAccountManager() { + if ($this->accountManager === null) { + $this->accountManager = \OC::$server->query(AccountManager::class); + } + return $this->accountManager; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Activity/ActivitySettingsAdapter.php b/docker/overlays/nextcloud/html/lib/private/Activity/ActivitySettingsAdapter.php new file mode 100644 index 0000000..2dd852f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Activity/ActivitySettingsAdapter.php @@ -0,0 +1,73 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Activity; + +use OCP\Activity\ActivitySettings; +use OCP\Activity\ISetting; +use OCP\IL10N; + +/** + * Adapt the old interface based settings into the new abstract + * class based one + */ +class ActivitySettingsAdapter extends ActivitySettings { + private $oldSettings; + private $l10n; + + public function __construct(ISetting $oldSettings, IL10N $l10n) { + $this->oldSettings = $oldSettings; + $this->l10n = $l10n; + } + + public function getIdentifier() { + return $this->oldSettings->getIdentifier(); + } + + public function getName() { + return $this->oldSettings->getName(); + } + + public function getGroupIdentifier() { + return 'other'; + } + + public function getGroupName() { + return $this->l10n->t('Other activities'); + } + + public function getPriority() { + return $this->oldSettings->getPriority(); + } + + public function canChangeMail() { + return $this->oldSettings->canChangeMail(); + } + + public function isDefaultEnabledMail() { + return $this->oldSettings->isDefaultEnabledMail(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Activity/Event.php b/docker/overlays/nextcloud/html/lib/private/Activity/Event.php new file mode 100644 index 0000000..8535561 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Activity/Event.php @@ -0,0 +1,547 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Phil Davis + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Activity; + +use OCP\Activity\IEvent; +use OCP\RichObjectStrings\InvalidObjectExeption; +use OCP\RichObjectStrings\IValidator; + +class Event implements IEvent { + + /** @var string */ + protected $app = ''; + /** @var string */ + protected $type = ''; + /** @var string */ + protected $affectedUser = ''; + /** @var string */ + protected $author = ''; + /** @var int */ + protected $timestamp = 0; + /** @var string */ + protected $subject = ''; + /** @var array */ + protected $subjectParameters = []; + /** @var string */ + protected $subjectParsed = ''; + /** @var string */ + protected $subjectRich = ''; + /** @var array */ + protected $subjectRichParameters = []; + /** @var string */ + protected $message = ''; + /** @var array */ + protected $messageParameters = []; + /** @var string */ + protected $messageParsed = ''; + /** @var string */ + protected $messageRich = ''; + /** @var array */ + protected $messageRichParameters = []; + /** @var string */ + protected $objectType = ''; + /** @var int */ + protected $objectId = 0; + /** @var string */ + protected $objectName = ''; + /** @var string */ + protected $link = ''; + /** @var string */ + protected $icon = ''; + /** @var bool */ + protected $generateNotification = true; + + /** @var IEvent|null */ + protected $child; + /** @var IValidator */ + protected $richValidator; + + /** + * @param IValidator $richValidator + */ + public function __construct(IValidator $richValidator) { + $this->richValidator = $richValidator; + } + + /** + * Set the app of the activity + * + * @param string $app + * @return IEvent + * @throws \InvalidArgumentException if the app id is invalid + * @since 8.2.0 + */ + public function setApp(string $app): IEvent { + if ($app === '' || isset($app[32])) { + throw new \InvalidArgumentException('The given app is invalid'); + } + $this->app = $app; + return $this; + } + + /** + * @return string + */ + public function getApp(): string { + return $this->app; + } + + /** + * Set the type of the activity + * + * @param string $type + * @return IEvent + * @throws \InvalidArgumentException if the type is invalid + * @since 8.2.0 + */ + public function setType(string $type): IEvent { + if ($type === '' || isset($type[255])) { + throw new \InvalidArgumentException('The given type is invalid'); + } + $this->type = $type; + return $this; + } + + /** + * @return string + */ + public function getType(): string { + return $this->type; + } + + /** + * Set the affected user of the activity + * + * @param string $affectedUser + * @return IEvent + * @throws \InvalidArgumentException if the affected user is invalid + * @since 8.2.0 + */ + public function setAffectedUser(string $affectedUser): IEvent { + if ($affectedUser === '' || isset($affectedUser[64])) { + throw new \InvalidArgumentException('The given affected user is invalid'); + } + $this->affectedUser = $affectedUser; + return $this; + } + + /** + * @return string + */ + public function getAffectedUser(): string { + return $this->affectedUser; + } + + /** + * Set the author of the activity + * + * @param string $author + * @return IEvent + * @throws \InvalidArgumentException if the author is invalid + * @since 8.2.0 + */ + public function setAuthor(string $author): IEvent { + if (isset($author[64])) { + throw new \InvalidArgumentException('The given author user is invalid'); + } + $this->author = $author; + return $this; + } + + /** + * @return string + */ + public function getAuthor(): string { + return $this->author; + } + + /** + * Set the timestamp of the activity + * + * @param int $timestamp + * @return IEvent + * @throws \InvalidArgumentException if the timestamp is invalid + * @since 8.2.0 + */ + public function setTimestamp(int $timestamp): IEvent { + $this->timestamp = $timestamp; + return $this; + } + + /** + * @return int + */ + public function getTimestamp(): int { + return $this->timestamp; + } + + /** + * Set the subject of the activity + * + * @param string $subject + * @param array $parameters + * @return IEvent + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 8.2.0 + */ + public function setSubject(string $subject, array $parameters = []): IEvent { + if (isset($subject[255])) { + throw new \InvalidArgumentException('The given subject is invalid'); + } + $this->subject = $subject; + $this->subjectParameters = $parameters; + return $this; + } + + /** + * @return string + */ + public function getSubject(): string { + return $this->subject; + } + + /** + * @return array + */ + public function getSubjectParameters(): array { + return $this->subjectParameters; + } + + /** + * @param string $subject + * @return $this + * @throws \InvalidArgumentException if the subject is invalid + * @since 11.0.0 + */ + public function setParsedSubject(string $subject): IEvent { + if ($subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + $this->subjectParsed = $subject; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedSubject(): string { + return $this->subjectParsed; + } + + /** + * @param string $subject + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichSubject(string $subject, array $parameters = []): IEvent { + if ($subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + $this->subjectRich = $subject; + $this->subjectRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichSubject(): string { + return $this->subjectRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichSubjectParameters(): array { + return $this->subjectRichParameters; + } + + /** + * Set the message of the activity + * + * @param string $message + * @param array $parameters + * @return IEvent + * @throws \InvalidArgumentException if the message or parameters are invalid + * @since 8.2.0 + */ + public function setMessage(string $message, array $parameters = []): IEvent { + if (isset($message[255])) { + throw new \InvalidArgumentException('The given message is invalid'); + } + $this->message = $message; + $this->messageParameters = $parameters; + return $this; + } + + /** + * @return string + */ + public function getMessage(): string { + return $this->message; + } + + /** + * @return array + */ + public function getMessageParameters(): array { + return $this->messageParameters; + } + + /** + * @param string $message + * @return $this + * @throws \InvalidArgumentException if the message is invalid + * @since 11.0.0 + */ + public function setParsedMessage(string $message): IEvent { + $this->messageParsed = $message; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedMessage(): string { + return $this->messageParsed; + } + + /** + * @param string $message + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichMessage(string $message, array $parameters = []): IEvent { + $this->messageRich = $message; + $this->messageRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichMessage(): string { + return $this->messageRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichMessageParameters(): array { + return $this->messageRichParameters; + } + + /** + * Set the object of the activity + * + * @param string $objectType + * @param int $objectId + * @param string $objectName + * @return IEvent + * @throws \InvalidArgumentException if the object is invalid + * @since 8.2.0 + */ + public function setObject(string $objectType, int $objectId, string $objectName = ''): IEvent { + if (isset($objectType[255])) { + throw new \InvalidArgumentException('The given object type is invalid'); + } + if (isset($objectName[4000])) { + throw new \InvalidArgumentException('The given object name is invalid'); + } + $this->objectType = $objectType; + $this->objectId = $objectId; + $this->objectName = $objectName; + return $this; + } + + /** + * @return string + */ + public function getObjectType(): string { + return $this->objectType; + } + + /** + * @return int + */ + public function getObjectId(): int { + return $this->objectId; + } + + /** + * @return string + */ + public function getObjectName(): string { + return $this->objectName; + } + + /** + * Set the link of the activity + * + * @param string $link + * @return IEvent + * @throws \InvalidArgumentException if the link is invalid + * @since 8.2.0 + */ + public function setLink(string $link): IEvent { + if (isset($link[4000])) { + throw new \InvalidArgumentException('The given link is invalid'); + } + $this->link = $link; + return $this; + } + + /** + * @return string + */ + public function getLink(): string { + return $this->link; + } + + /** + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 11.0.0 + */ + public function setIcon(string $icon): IEvent { + if (isset($icon[4000])) { + throw new \InvalidArgumentException('The given icon is invalid'); + } + $this->icon = $icon; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getIcon(): string { + return $this->icon; + } + + /** + * @param IEvent $child + * @return $this + * @since 11.0.0 - Since 15.0.0 returns $this + */ + public function setChildEvent(IEvent $child): IEvent { + $this->child = $child; + return $this; + } + + /** + * @return IEvent|null + * @since 11.0.0 + */ + public function getChildEvent() { + return $this->child; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValid(): bool { + return + $this->isValidCommon() + && + $this->getSubject() !== '' + ; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValidParsed(): bool { + if ($this->getRichSubject() !== '' || !empty($this->getRichSubjectParameters())) { + try { + $this->richValidator->validate($this->getRichSubject(), $this->getRichSubjectParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + if ($this->getRichMessage() !== '' || !empty($this->getRichMessageParameters())) { + try { + $this->richValidator->validate($this->getRichMessage(), $this->getRichMessageParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + return + $this->isValidCommon() + && + $this->getParsedSubject() !== '' + ; + } + + protected function isValidCommon(): bool { + return + $this->getApp() !== '' + && + $this->getType() !== '' + && + $this->getAffectedUser() !== '' + && + $this->getTimestamp() !== 0 + /** + * Disabled for BC with old activities + * && + * $this->getObjectType() !== '' + * && + * $this->getObjectId() !== 0 + */ + ; + } + + public function setGenerateNotification(bool $generate): IEvent { + $this->generateNotification = $generate; + return $this; + } + + public function getGenerateNotification(): bool { + return $this->generateNotification; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Activity/EventMerger.php b/docker/overlays/nextcloud/html/lib/private/Activity/EventMerger.php new file mode 100644 index 0000000..d2caad4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Activity/EventMerger.php @@ -0,0 +1,261 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Activity; + +use OCP\Activity\IEvent; +use OCP\Activity\IEventMerger; +use OCP\IL10N; + +class EventMerger implements IEventMerger { + + /** @var IL10N */ + protected $l10n; + + /** + * @param IL10N $l10n + */ + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * Combines two events when possible to have grouping: + * + * Example1: Two events with subject '{user} created {file}' and + * $mergeParameter file with different file and same user will be merged + * to '{user} created {file1} and {file2}' and the childEvent on the return + * will be set, if the events have been merged. + * + * Example2: Two events with subject '{user} created {file}' and + * $mergeParameter file with same file and same user will be merged to + * '{user} created {file1}' and the childEvent on the return will be set, if + * the events have been merged. + * + * The following requirements have to be met, in order to be merged: + * - Both events need to have the same `getApp()` + * - Both events must not have a message `getMessage()` + * - Both events need to have the same subject `getSubject()` + * - Both events need to have the same object type `getObjectType()` + * - The time difference between both events must not be bigger then 3 hours + * - Only up to 5 events can be merged. + * - All parameters apart from such starting with $mergeParameter must be + * the same for both events. + * + * @param string $mergeParameter + * @param IEvent $event + * @param IEvent|null $previousEvent + * @return IEvent + */ + public function mergeEvents($mergeParameter, IEvent $event, IEvent $previousEvent = null) { + // No second event => can not combine + if (!$previousEvent instanceof IEvent) { + return $event; + } + + // Different app => can not combine + if ($event->getApp() !== $previousEvent->getApp()) { + return $event; + } + + // Message is set => can not combine + if ($event->getMessage() !== '' || $previousEvent->getMessage() !== '') { + return $event; + } + + // Different subject => can not combine + if ($event->getSubject() !== $previousEvent->getSubject()) { + return $event; + } + + // Different object type => can not combine + if ($event->getObjectType() !== $previousEvent->getObjectType()) { + return $event; + } + + // More than 3 hours difference => can not combine + if (abs($event->getTimestamp() - $previousEvent->getTimestamp()) > 3 * 60 * 60) { + return $event; + } + + // Other parameters are not the same => can not combine + try { + list($combined, $parameters) = $this->combineParameters($mergeParameter, $event, $previousEvent); + } catch (\UnexpectedValueException $e) { + return $event; + } + + try { + $newSubject = $this->getExtendedSubject($event->getRichSubject(), $mergeParameter, $combined); + $parsedSubject = $this->generateParsedSubject($newSubject, $parameters); + + $event->setRichSubject($newSubject, $parameters) + ->setParsedSubject($parsedSubject) + ->setChildEvent($previousEvent) + ->setTimestamp(max($event->getTimestamp(), $previousEvent->getTimestamp())); + } catch (\UnexpectedValueException $e) { + return $event; + } + + return $event; + } + + /** + * @param string $mergeParameter + * @param IEvent $event + * @param IEvent $previousEvent + * @return array + * @throws \UnexpectedValueException + */ + protected function combineParameters($mergeParameter, IEvent $event, IEvent $previousEvent) { + $params1 = $event->getRichSubjectParameters(); + $params2 = $previousEvent->getRichSubjectParameters(); + $params = []; + + $combined = 0; + + // Check that all parameters from $event exist in $previousEvent + foreach ($params1 as $key => $parameter) { + if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) { + if (!$this->checkParameterAlreadyExits($params, $mergeParameter, $parameter)) { + $combined++; + $params[$mergeParameter . $combined] = $parameter; + } + continue; + } + + if (!isset($params2[$key]) || $params2[$key] !== $parameter) { + // Parameter missing on $previousEvent or different => can not combine + throw new \UnexpectedValueException(); + } + + $params[$key] = $parameter; + } + + // Check that all parameters from $previousEvent exist in $event + foreach ($params2 as $key => $parameter) { + if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) { + if (!$this->checkParameterAlreadyExits($params, $mergeParameter, $parameter)) { + $combined++; + $params[$mergeParameter . $combined] = $parameter; + } + continue; + } + + if (!isset($params1[$key]) || $params1[$key] !== $parameter) { + // Parameter missing on $event or different => can not combine + throw new \UnexpectedValueException(); + } + + $params[$key] = $parameter; + } + + return [$combined, $params]; + } + + /** + * @param array[] $parameters + * @param string $mergeParameter + * @param array $parameter + * @return bool + */ + protected function checkParameterAlreadyExits($parameters, $mergeParameter, $parameter) { + foreach ($parameters as $key => $param) { + if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) { + if ($param === $parameter) { + return true; + } + } + } + return false; + } + + /** + * @param string $subject + * @param string $parameter + * @param int $counter + * @return mixed + */ + protected function getExtendedSubject($subject, $parameter, $counter) { + switch ($counter) { + case 1: + $replacement = '{' . $parameter . '1}'; + break; + case 2: + $replacement = $this->l10n->t( + '%1$s and %2$s', + ['{' . $parameter . '2}', '{' . $parameter . '1}'] + ); + break; + case 3: + $replacement = $this->l10n->t( + '%1$s, %2$s and %3$s', + ['{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}'] + ); + break; + case 4: + $replacement = $this->l10n->t( + '%1$s, %2$s, %3$s and %4$s', + ['{' . $parameter . '4}', '{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}'] + ); + break; + case 5: + $replacement = $this->l10n->t( + '%1$s, %2$s, %3$s, %4$s and %5$s', + ['{' . $parameter . '5}', '{' . $parameter . '4}', '{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}'] + ); + break; + default: + throw new \UnexpectedValueException(); + } + + return str_replace( + '{' . $parameter . '}', + $replacement, + $subject + ); + } + + /** + * @param string $subject + * @param array[] $parameters + * @return string + */ + protected function generateParsedSubject($subject, $parameters) { + $placeholders = $replacements = []; + foreach ($parameters as $placeholder => $parameter) { + $placeholders[] = '{' . $placeholder . '}'; + if ($parameter['type'] === 'file') { + $replacements[] = trim($parameter['path'], '/'); + } elseif (isset($parameter['name'])) { + $replacements[] = $parameter['name']; + } else { + $replacements[] = $parameter['id']; + } + } + + return str_replace($placeholders, $replacements, $subject); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Activity/Manager.php b/docker/overlays/nextcloud/html/lib/private/Activity/Manager.php new file mode 100644 index 0000000..25b666e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Activity/Manager.php @@ -0,0 +1,401 @@ + + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Activity; + +use OCP\Activity\ActivitySettings; +use OCP\Activity\IConsumer; +use OCP\Activity\IEvent; +use OCP\Activity\IFilter; +use OCP\Activity\IManager; +use OCP\Activity\IProvider; +use OCP\Activity\ISetting; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; +use OCP\RichObjectStrings\IValidator; + +class Manager implements IManager { + /** @var IRequest */ + protected $request; + + /** @var IUserSession */ + protected $session; + + /** @var IConfig */ + protected $config; + + /** @var IValidator */ + protected $validator; + + /** @var string */ + protected $formattingObjectType; + + /** @var int */ + protected $formattingObjectId; + + /** @var bool */ + protected $requirePNG = false; + + /** @var string */ + protected $currentUserId; + + protected $l10n; + + public function __construct( + IRequest $request, + IUserSession $session, + IConfig $config, + IValidator $validator, + IL10N $l10n + ) { + $this->request = $request; + $this->session = $session; + $this->config = $config; + $this->validator = $validator; + $this->l10n = $l10n; + } + + /** @var \Closure[] */ + private $consumersClosures = []; + + /** @var IConsumer[] */ + private $consumers = []; + + /** + * @return \OCP\Activity\IConsumer[] + */ + protected function getConsumers(): array { + if (!empty($this->consumers)) { + return $this->consumers; + } + + $this->consumers = []; + foreach ($this->consumersClosures as $consumer) { + $c = $consumer(); + if ($c instanceof IConsumer) { + $this->consumers[] = $c; + } else { + throw new \InvalidArgumentException('The given consumer does not implement the \OCP\Activity\IConsumer interface'); + } + } + + return $this->consumers; + } + + /** + * Generates a new IEvent object + * + * Make sure to call at least the following methods before sending it to the + * app with via the publish() method: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @return IEvent + */ + public function generateEvent(): IEvent { + return new Event($this->validator); + } + + /** + * Publish an event to the activity consumers + * + * Make sure to call at least the following methods before sending an Event: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @param IEvent $event + * @throws \BadMethodCallException if required values have not been set + */ + public function publish(IEvent $event): void { + if ($event->getAuthor() === '') { + if ($this->session->getUser() instanceof IUser) { + $event->setAuthor($this->session->getUser()->getUID()); + } + } + + if (!$event->getTimestamp()) { + $event->setTimestamp(time()); + } + + if (!$event->isValid()) { + throw new \BadMethodCallException('The given event is invalid'); + } + + foreach ($this->getConsumers() as $c) { + $c->receive($event); + } + } + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * activity consumers are actually requested + * + * $callable has to return an instance of OCA\Activity\IConsumer + * + * @param \Closure $callable + */ + public function registerConsumer(\Closure $callable): void { + $this->consumersClosures[] = $callable; + $this->consumers = []; + } + + /** @var string[] */ + protected $filterClasses = []; + + /** @var IFilter[] */ + protected $filters = []; + + /** + * @param string $filter Class must implement OCA\Activity\IFilter + * @return void + */ + public function registerFilter(string $filter): void { + $this->filterClasses[$filter] = false; + } + + /** + * @return IFilter[] + * @throws \InvalidArgumentException + */ + public function getFilters(): array { + foreach ($this->filterClasses as $class => $false) { + /** @var IFilter $filter */ + $filter = \OC::$server->query($class); + + if (!$filter instanceof IFilter) { + throw new \InvalidArgumentException('Invalid activity filter registered'); + } + + $this->filters[$filter->getIdentifier()] = $filter; + + unset($this->filterClasses[$class]); + } + return $this->filters; + } + + /** + * @param string $id + * @return IFilter + * @throws \InvalidArgumentException when the filter was not found + * @since 11.0.0 + */ + public function getFilterById(string $id): IFilter { + $filters = $this->getFilters(); + + if (isset($filters[$id])) { + return $filters[$id]; + } + + throw new \InvalidArgumentException('Requested filter does not exist'); + } + + /** @var string[] */ + protected $providerClasses = []; + + /** @var IProvider[] */ + protected $providers = []; + + /** + * @param string $provider Class must implement OCA\Activity\IProvider + * @return void + */ + public function registerProvider(string $provider): void { + $this->providerClasses[$provider] = false; + } + + /** + * @return IProvider[] + * @throws \InvalidArgumentException + */ + public function getProviders(): array { + foreach ($this->providerClasses as $class => $false) { + /** @var IProvider $provider */ + $provider = \OC::$server->query($class); + + if (!$provider instanceof IProvider) { + throw new \InvalidArgumentException('Invalid activity provider registered'); + } + + $this->providers[] = $provider; + + unset($this->providerClasses[$class]); + } + return $this->providers; + } + + /** @var string[] */ + protected $settingsClasses = []; + + /** @var ISetting[] */ + protected $settings = []; + + /** + * @param string $setting Class must implement OCA\Activity\ISetting + * @return void + */ + public function registerSetting(string $setting): void { + $this->settingsClasses[$setting] = false; + } + + /** + * @return ActivitySettings[] + * @throws \InvalidArgumentException + */ + public function getSettings(): array { + foreach ($this->settingsClasses as $class => $false) { + /** @var ISetting $setting */ + $setting = \OC::$server->query($class); + + if ($setting instanceof ISetting) { + if (!$setting instanceof ActivitySettings) { + $setting = new ActivitySettingsAdapter($setting, $this->l10n); + } + } else { + throw new \InvalidArgumentException('Invalid activity filter registered'); + } + + $this->settings[$setting->getIdentifier()] = $setting; + + unset($this->settingsClasses[$class]); + } + return $this->settings; + } + + /** + * @param string $id + * @return ActivitySettings + * @throws \InvalidArgumentException when the setting was not found + * @since 11.0.0 + */ + public function getSettingById(string $id): ActivitySettings { + $settings = $this->getSettings(); + + if (isset($settings[$id])) { + return $settings[$id]; + } + + throw new \InvalidArgumentException('Requested setting does not exist'); + } + + + /** + * @param string $type + * @param int $id + */ + public function setFormattingObject(string $type, int $id): void { + $this->formattingObjectType = $type; + $this->formattingObjectId = $id; + } + + /** + * @return bool + */ + public function isFormattingFilteredObject(): bool { + return $this->formattingObjectType !== null && $this->formattingObjectId !== null + && $this->formattingObjectType === $this->request->getParam('object_type') + && $this->formattingObjectId === (int) $this->request->getParam('object_id'); + } + + /** + * @param bool $status Set to true, when parsing events should not use SVG icons + */ + public function setRequirePNG(bool $status): void { + $this->requirePNG = $status; + } + + /** + * @return bool + */ + public function getRequirePNG(): bool { + return $this->requirePNG; + } + + /** + * Set the user we need to use + * + * @param string|null $currentUserId + * @throws \UnexpectedValueException If the user is invalid + */ + public function setCurrentUserId(string $currentUserId = null): void { + if (!is_string($currentUserId) && $currentUserId !== null) { + throw new \UnexpectedValueException('The given current user is invalid'); + } + $this->currentUserId = $currentUserId; + } + + /** + * Get the user we need to use + * + * Either the user is logged in, or we try to get it from the token + * + * @return string + * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + */ + public function getCurrentUserId(): string { + if ($this->currentUserId !== null) { + return $this->currentUserId; + } + + if (!$this->session->isLoggedIn()) { + return $this->getUserFromToken(); + } + + return $this->session->getUser()->getUID(); + } + + /** + * Get the user for the token + * + * @return string + * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + */ + protected function getUserFromToken(): string { + $token = (string) $this->request->getParam('token', ''); + if (strlen($token) !== 30) { + throw new \UnexpectedValueException('The token is invalid'); + } + + $users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token); + + if (count($users) !== 1) { + // No unique user found + throw new \UnexpectedValueException('The token is invalid'); + } + + // Token found login as that user + return array_shift($users); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AllConfig.php b/docker/overlays/nextcloud/html/lib/private/AllConfig.php new file mode 100644 index 0000000..20f4afd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AllConfig.php @@ -0,0 +1,541 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Loki3000 + * @author Lukas Reschke + * @author MichaIng + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\Cache\CappedMemoryCache; +use OCP\IDBConnection; +use OCP\PreConditionNotMetException; + +/** + * Class to combine all the configuration options ownCloud offers + */ +class AllConfig implements \OCP\IConfig { + /** @var SystemConfig */ + private $systemConfig; + + /** @var IDBConnection */ + private $connection; + + /** + * 3 dimensional array with the following structure: + * [ $userId => + * [ $appId => + * [ $key => $value ] + * ] + * ] + * + * database table: preferences + * + * methods that use this: + * - setUserValue + * - getUserValue + * - getUserKeys + * - deleteUserValue + * - deleteAllUserValues + * - deleteAppFromAllUsers + * + * @var CappedMemoryCache $userCache + */ + private $userCache; + + /** + * @param SystemConfig $systemConfig + */ + public function __construct(SystemConfig $systemConfig) { + $this->userCache = new CappedMemoryCache(); + $this->systemConfig = $systemConfig; + } + + /** + * TODO - FIXME This fixes an issue with base.php that cause cyclic + * dependencies, especially with autoconfig setup + * + * Replace this by properly injected database connection. Currently the + * base.php triggers the getDatabaseConnection too early which causes in + * autoconfig setup case a too early distributed database connection and + * the autoconfig then needs to reinit all already initialized dependencies + * that use the database connection. + * + * otherwise a SQLite database is created in the wrong directory + * because the database connection was created with an uninitialized config + */ + private function fixDIInit() { + if ($this->connection === null) { + $this->connection = \OC::$server->getDatabaseConnection(); + } + } + + /** + * Sets and deletes system wide values + * + * @param array $configs Associative array with `key => value` pairs + * If value is null, the config key will be deleted + */ + public function setSystemValues(array $configs) { + $this->systemConfig->setValues($configs); + } + + /** + * Sets a new system wide value + * + * @param string $key the key of the value, under which will be saved + * @param mixed $value the value that should be stored + */ + public function setSystemValue($key, $value) { + $this->systemConfig->setValue($key, $value); + } + + /** + * Looks up a system wide defined value + * + * @param string $key the key of the value, under which it was saved + * @param mixed $default the default value to be returned if the value isn't set + * @return mixed the value or $default + */ + public function getSystemValue($key, $default = '') { + return $this->systemConfig->getValue($key, $default); + } + + /** + * Looks up a boolean system wide defined value + * + * @param string $key the key of the value, under which it was saved + * @param bool $default the default value to be returned if the value isn't set + * + * @return bool + * + * @since 16.0.0 + */ + public function getSystemValueBool(string $key, bool $default = false): bool { + return (bool) $this->getSystemValue($key, $default); + } + + /** + * Looks up an integer system wide defined value + * + * @param string $key the key of the value, under which it was saved + * @param int $default the default value to be returned if the value isn't set + * + * @return int + * + * @since 16.0.0 + */ + public function getSystemValueInt(string $key, int $default = 0): int { + return (int) $this->getSystemValue($key, $default); + } + + /** + * Looks up a string system wide defined value + * + * @param string $key the key of the value, under which it was saved + * @param string $default the default value to be returned if the value isn't set + * + * @return string + * + * @since 16.0.0 + */ + public function getSystemValueString(string $key, string $default = ''): string { + return (string) $this->getSystemValue($key, $default); + } + + /** + * Looks up a system wide defined value and filters out sensitive data + * + * @param string $key the key of the value, under which it was saved + * @param mixed $default the default value to be returned if the value isn't set + * @return mixed the value or $default + */ + public function getFilteredSystemValue($key, $default = '') { + return $this->systemConfig->getFilteredValue($key, $default); + } + + /** + * Delete a system wide defined value + * + * @param string $key the key of the value, under which it was saved + */ + public function deleteSystemValue($key) { + $this->systemConfig->deleteValue($key); + } + + /** + * Get all keys stored for an app + * + * @param string $appName the appName that we stored the value under + * @return string[] the keys stored for the app + */ + public function getAppKeys($appName) { + return \OC::$server->query(\OC\AppConfig::class)->getKeys($appName); + } + + /** + * Writes a new app wide value + * + * @param string $appName the appName that we want to store the value under + * @param string $key the key of the value, under which will be saved + * @param string|float|int $value the value that should be stored + */ + public function setAppValue($appName, $key, $value) { + \OC::$server->query(\OC\AppConfig::class)->setValue($appName, $key, $value); + } + + /** + * Looks up an app wide defined value + * + * @param string $appName the appName that we stored the value under + * @param string $key the key of the value, under which it was saved + * @param string $default the default value to be returned if the value isn't set + * @return string the saved value + */ + public function getAppValue($appName, $key, $default = '') { + return \OC::$server->query(\OC\AppConfig::class)->getValue($appName, $key, $default); + } + + /** + * Delete an app wide defined value + * + * @param string $appName the appName that we stored the value under + * @param string $key the key of the value, under which it was saved + */ + public function deleteAppValue($appName, $key) { + \OC::$server->query(\OC\AppConfig::class)->deleteKey($appName, $key); + } + + /** + * Removes all keys in appconfig belonging to the app + * + * @param string $appName the appName the configs are stored under + */ + public function deleteAppValues($appName) { + \OC::$server->query(\OC\AppConfig::class)->deleteApp($appName); + } + + + /** + * Set a user defined value + * + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we want to store the value under + * @param string $key the key under which the value is being stored + * @param string|float|int $value the value that you want to store + * @param string $preCondition only update if the config value was previously the value passed as $preCondition + * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met + * @throws \UnexpectedValueException when trying to store an unexpected value + */ + public function setUserValue($userId, $appName, $key, $value, $preCondition = null) { + if (!is_int($value) && !is_float($value) && !is_string($value)) { + throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value'); + } + + // TODO - FIXME + $this->fixDIInit(); + + $prevValue = $this->getUserValue($userId, $appName, $key, null); + + if ($prevValue !== null) { + if ($prevValue === (string)$value) { + return; + } elseif ($preCondition !== null && $prevValue !== (string)$preCondition) { + throw new PreConditionNotMetException(); + } else { + $qb = $this->connection->getQueryBuilder(); + $qb->update('preferences') + ->set('configvalue', $qb->createNamedParameter($value)) + ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName))) + ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key))); + $qb->execute(); + + $this->userCache[$userId][$appName][$key] = (string)$value; + return; + } + } + + $preconditionArray = []; + if (isset($preCondition)) { + $preconditionArray = [ + 'configvalue' => $preCondition, + ]; + } + + $this->connection->setValues('preferences', [ + 'userid' => $userId, + 'appid' => $appName, + 'configkey' => $key, + ], [ + 'configvalue' => $value, + ], $preconditionArray); + + // only add to the cache if we already loaded data for the user + if (isset($this->userCache[$userId])) { + if (!isset($this->userCache[$userId][$appName])) { + $this->userCache[$userId][$appName] = []; + } + $this->userCache[$userId][$appName][$key] = (string)$value; + } + } + + /** + * Getting a user defined value + * + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we stored the value under + * @param string $key the key under which the value is being stored + * @param mixed $default the default value to be returned if the value isn't set + * @return string + */ + public function getUserValue($userId, $appName, $key, $default = '') { + $data = $this->getUserValues($userId); + if (isset($data[$appName][$key])) { + return $data[$appName][$key]; + } else { + return $default; + } + } + + /** + * Get the keys of all stored by an app for the user + * + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we stored the value under + * @return string[] + */ + public function getUserKeys($userId, $appName) { + $data = $this->getUserValues($userId); + if (isset($data[$appName])) { + return array_keys($data[$appName]); + } else { + return []; + } + } + + /** + * Delete a user value + * + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we stored the value under + * @param string $key the key under which the value is being stored + */ + public function deleteUserValue($userId, $appName, $key) { + // TODO - FIXME + $this->fixDIInit(); + + $sql = 'DELETE FROM `*PREFIX*preferences` '. + 'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?'; + $this->connection->executeUpdate($sql, [$userId, $appName, $key]); + + if (isset($this->userCache[$userId][$appName])) { + unset($this->userCache[$userId][$appName][$key]); + } + } + + /** + * Delete all user values + * + * @param string $userId the userId of the user that we want to remove all values from + */ + public function deleteAllUserValues($userId) { + // TODO - FIXME + $this->fixDIInit(); + + $sql = 'DELETE FROM `*PREFIX*preferences` '. + 'WHERE `userid` = ?'; + $this->connection->executeUpdate($sql, [$userId]); + + unset($this->userCache[$userId]); + } + + /** + * Delete all user related values of one app + * + * @param string $appName the appName of the app that we want to remove all values from + */ + public function deleteAppFromAllUsers($appName) { + // TODO - FIXME + $this->fixDIInit(); + + $sql = 'DELETE FROM `*PREFIX*preferences` '. + 'WHERE `appid` = ?'; + $this->connection->executeUpdate($sql, [$appName]); + + foreach ($this->userCache as &$userCache) { + unset($userCache[$appName]); + } + } + + /** + * Returns all user configs sorted by app of one user + * + * @param string $userId the user ID to get the app configs from + * @return array[] - 2 dimensional array with the following structure: + * [ $appId => + * [ $key => $value ] + * ] + */ + private function getUserValues($userId) { + if (isset($this->userCache[$userId])) { + return $this->userCache[$userId]; + } + if ($userId === null || $userId === '') { + $this->userCache[$userId]=[]; + return $this->userCache[$userId]; + } + + // TODO - FIXME + $this->fixDIInit(); + + $data = []; + $query = 'SELECT `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?'; + $result = $this->connection->executeQuery($query, [$userId]); + while ($row = $result->fetch()) { + $appId = $row['appid']; + if (!isset($data[$appId])) { + $data[$appId] = []; + } + $data[$appId][$row['configkey']] = $row['configvalue']; + } + $this->userCache[$userId] = $data; + return $data; + } + + /** + * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs. + * + * @param string $appName app to get the value for + * @param string $key the key to get the value for + * @param array $userIds the user IDs to fetch the values for + * @return array Mapped values: userId => value + */ + public function getUserValueForUsers($appName, $key, $userIds) { + // TODO - FIXME + $this->fixDIInit(); + + if (empty($userIds) || !is_array($userIds)) { + return []; + } + + $chunkedUsers = array_chunk($userIds, 50, true); + $placeholders50 = implode(',', array_fill(0, 50, '?')); + + $userValues = []; + foreach ($chunkedUsers as $chunk) { + $queryParams = $chunk; + // create [$app, $key, $chunkedUsers] + array_unshift($queryParams, $key); + array_unshift($queryParams, $appName); + + $placeholders = (count($chunk) === 50) ? $placeholders50 : implode(',', array_fill(0, count($chunk), '?')); + + $query = 'SELECT `userid`, `configvalue` ' . + 'FROM `*PREFIX*preferences` ' . + 'WHERE `appid` = ? AND `configkey` = ? ' . + 'AND `userid` IN (' . $placeholders . ')'; + $result = $this->connection->executeQuery($query, $queryParams); + + while ($row = $result->fetch()) { + $userValues[$row['userid']] = $row['configvalue']; + } + } + + return $userValues; + } + + /** + * Determines the users that have the given value set for a specific app-key-pair + * + * @param string $appName the app to get the user for + * @param string $key the key to get the user for + * @param string $value the value to get the user for + * @return array of user IDs + */ + public function getUsersForUserValue($appName, $key, $value) { + // TODO - FIXME + $this->fixDIInit(); + + $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . + 'WHERE `appid` = ? AND `configkey` = ? '; + + if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') { + //oracle hack: need to explicitly cast CLOB to CHAR for comparison + $sql .= 'AND to_char(`configvalue`) = ?'; + } else { + $sql .= 'AND `configvalue` = ?'; + } + + $result = $this->connection->executeQuery($sql, [$appName, $key, $value]); + + $userIDs = []; + while ($row = $result->fetch()) { + $userIDs[] = $row['userid']; + } + + return $userIDs; + } + + /** + * Determines the users that have the given value set for a specific app-key-pair + * + * @param string $appName the app to get the user for + * @param string $key the key to get the user for + * @param string $value the value to get the user for + * @return array of user IDs + */ + public function getUsersForUserValueCaseInsensitive($appName, $key, $value) { + // TODO - FIXME + $this->fixDIInit(); + + $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . + 'WHERE `appid` = ? AND `configkey` = ? '; + + if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') { + //oracle hack: need to explicitly cast CLOB to CHAR for comparison + $sql .= 'AND LOWER(to_char(`configvalue`)) = LOWER(?)'; + } else { + $sql .= 'AND LOWER(`configvalue`) = LOWER(?)'; + } + + $result = $this->connection->executeQuery($sql, [$appName, $key, $value]); + + $userIDs = []; + while ($row = $result->fetch()) { + $userIDs[] = $row['userid']; + } + + return $userIDs; + } + + public function getSystemConfig() { + return $this->systemConfig; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppManager.php b/docker/overlays/nextcloud/html/lib/private/App/AppManager.php new file mode 100644 index 0000000..f756664 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppManager.php @@ -0,0 +1,592 @@ + + * @author Bjoern Schiessle + * @author Christoph Schaefer "christophł@wolkesicher.de" + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Daniel Rudolf + * @author Greta Doci + * @author Joas Schilling + * @author Julius Haertl + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Tobia De Koninck + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App; + +use OC\AppConfig; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use OCP\App\ManagerEvent; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class AppManager implements IAppManager { + + /** + * Apps with these types can not be enabled for certain groups only + * @var string[] + */ + protected $protectedAppTypes = [ + 'filesystem', + 'prelogin', + 'authentication', + 'logging', + 'prevent_group_restriction', + ]; + + /** @var IUserSession */ + private $userSession; + + /** @var IConfig */ + private $config; + + /** @var AppConfig */ + private $appConfig; + + /** @var IGroupManager */ + private $groupManager; + + /** @var ICacheFactory */ + private $memCacheFactory; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + /** @var ILogger */ + private $logger; + + /** @var string[] $appId => $enabled */ + private $installedAppsCache; + + /** @var string[] */ + private $shippedApps; + + /** @var string[] */ + private $alwaysEnabled; + + /** @var array */ + private $appInfos = []; + + /** @var array */ + private $appVersions = []; + + /** @var array */ + private $autoDisabledApps = []; + + /** + * @param IUserSession $userSession + * @param IConfig $config + * @param AppConfig $appConfig + * @param IGroupManager $groupManager + * @param ICacheFactory $memCacheFactory + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(IUserSession $userSession, + IConfig $config, + AppConfig $appConfig, + IGroupManager $groupManager, + ICacheFactory $memCacheFactory, + EventDispatcherInterface $dispatcher, + ILogger $logger) { + $this->userSession = $userSession; + $this->config = $config; + $this->appConfig = $appConfig; + $this->groupManager = $groupManager; + $this->memCacheFactory = $memCacheFactory; + $this->dispatcher = $dispatcher; + $this->logger = $logger; + } + + /** + * @return string[] $appId => $enabled + */ + private function getInstalledAppsValues() { + if (!$this->installedAppsCache) { + $values = $this->appConfig->getValues(false, 'enabled'); + + $alwaysEnabledApps = $this->getAlwaysEnabledApps(); + foreach ($alwaysEnabledApps as $appId) { + $values[$appId] = 'yes'; + } + + $this->installedAppsCache = array_filter($values, function ($value) { + return $value !== 'no'; + }); + ksort($this->installedAppsCache); + } + return $this->installedAppsCache; + } + + /** + * List all installed apps + * + * @return string[] + */ + public function getInstalledApps() { + return array_keys($this->getInstalledAppsValues()); + } + + /** + * List all apps enabled for a user + * + * @param \OCP\IUser $user + * @return string[] + */ + public function getEnabledAppsForUser(IUser $user) { + $apps = $this->getInstalledAppsValues(); + $appsForUser = array_filter($apps, function ($enabled) use ($user) { + return $this->checkAppForUser($enabled, $user); + }); + return array_keys($appsForUser); + } + + /** + * @param \OCP\IGroup $group + * @return array + */ + public function getEnabledAppsForGroup(IGroup $group): array { + $apps = $this->getInstalledAppsValues(); + $appsForGroups = array_filter($apps, function ($enabled) use ($group) { + return $this->checkAppForGroups($enabled, $group); + }); + return array_keys($appsForGroups); + } + + /** + * @return array + */ + public function getAutoDisabledApps(): array { + return $this->autoDisabledApps; + } + + /** + * @param string $appId + * @return array + */ + public function getAppRestriction(string $appId): array { + $values = $this->getInstalledAppsValues(); + + if (!isset($values[$appId])) { + return []; + } + + if ($values[$appId] === 'yes' || $values[$appId] === 'no') { + return []; + } + return json_decode($values[$appId]); + } + + + /** + * Check if an app is enabled for user + * + * @param string $appId + * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used + * @return bool + */ + public function isEnabledForUser($appId, $user = null) { + if ($this->isAlwaysEnabled($appId)) { + return true; + } + if ($user === null) { + $user = $this->userSession->getUser(); + } + $installedApps = $this->getInstalledAppsValues(); + if (isset($installedApps[$appId])) { + return $this->checkAppForUser($installedApps[$appId], $user); + } else { + return false; + } + } + + /** + * @param string $enabled + * @param IUser $user + * @return bool + */ + private function checkAppForUser($enabled, $user) { + if ($enabled === 'yes') { + return true; + } elseif ($user === null) { + return false; + } else { + if (empty($enabled)) { + return false; + } + + $groupIds = json_decode($enabled); + + if (!is_array($groupIds)) { + $jsonError = json_last_error(); + $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']); + return false; + } + + $userGroups = $this->groupManager->getUserGroupIds($user); + foreach ($userGroups as $groupId) { + if (in_array($groupId, $groupIds, true)) { + return true; + } + } + return false; + } + } + + /** + * @param string $enabled + * @param IGroup $group + * @return bool + */ + private function checkAppForGroups(string $enabled, IGroup $group): bool { + if ($enabled === 'yes') { + return true; + } elseif ($group === null) { + return false; + } else { + if (empty($enabled)) { + return false; + } + + $groupIds = json_decode($enabled); + + if (!is_array($groupIds)) { + $jsonError = json_last_error(); + $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']); + return false; + } + + return in_array($group->getGID(), $groupIds); + } + } + + /** + * Check if an app is enabled in the instance + * + * Notice: This actually checks if the app is enabled and not only if it is installed. + * + * @param string $appId + * @param \OCP\IGroup[]|String[] $groups + * @return bool + */ + public function isInstalled($appId) { + $installedApps = $this->getInstalledAppsValues(); + return isset($installedApps[$appId]); + } + + public function ignoreNextcloudRequirementForApp(string $appId): void { + $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []); + if (!in_array($appId, $ignoreMaxApps, true)) { + $ignoreMaxApps[] = $appId; + $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps); + } + } + + /** + * Enable an app for every user + * + * @param string $appId + * @param bool $forceEnable + * @throws AppPathNotFoundException + */ + public function enableApp(string $appId, bool $forceEnable = false): void { + // Check if app exists + $this->getAppPath($appId); + + if ($forceEnable) { + $this->ignoreNextcloudRequirementForApp($appId); + } + + $this->installedAppsCache[$appId] = 'yes'; + $this->appConfig->setValue($appId, 'enabled', 'yes'); + $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent( + ManagerEvent::EVENT_APP_ENABLE, $appId + )); + $this->clearAppsCache(); + } + + /** + * Whether a list of types contains a protected app type + * + * @param string[] $types + * @return bool + */ + public function hasProtectedAppType($types) { + if (empty($types)) { + return false; + } + + $protectedTypes = array_intersect($this->protectedAppTypes, $types); + return !empty($protectedTypes); + } + + /** + * Enable an app only for specific groups + * + * @param string $appId + * @param \OCP\IGroup[] $groups + * @param bool $forceEnable + * @throws \InvalidArgumentException if app can't be enabled for groups + * @throws AppPathNotFoundException + */ + public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void { + // Check if app exists + $this->getAppPath($appId); + + $info = $this->getAppInfo($appId); + if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) { + throw new \InvalidArgumentException("$appId can't be enabled for groups."); + } + + if ($forceEnable) { + $this->ignoreNextcloudRequirementForApp($appId); + } + + $groupIds = array_map(function ($group) { + /** @var \OCP\IGroup $group */ + return ($group instanceof IGroup) + ? $group->getGID() + : $group; + }, $groups); + + $this->installedAppsCache[$appId] = json_encode($groupIds); + $this->appConfig->setValue($appId, 'enabled', json_encode($groupIds)); + $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent( + ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups + )); + $this->clearAppsCache(); + } + + /** + * Disable an app for every user + * + * @param string $appId + * @param bool $automaticDisabled + * @throws \Exception if app can't be disabled + */ + public function disableApp($appId, $automaticDisabled = false) { + if ($this->isAlwaysEnabled($appId)) { + throw new \Exception("$appId can't be disabled."); + } + + if ($automaticDisabled) { + $this->autoDisabledApps[] = $appId; + } + + unset($this->installedAppsCache[$appId]); + $this->appConfig->setValue($appId, 'enabled', 'no'); + + // run uninstall steps + $appData = $this->getAppInfo($appId); + if (!is_null($appData)) { + \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']); + } + + $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent( + ManagerEvent::EVENT_APP_DISABLE, $appId + )); + $this->clearAppsCache(); + } + + /** + * Get the directory for the given app. + * + * @param string $appId + * @return string + * @throws AppPathNotFoundException if app folder can't be found + */ + public function getAppPath($appId) { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new AppPathNotFoundException('Could not find path for ' . $appId); + } + return $appPath; + } + + /** + * Get the web path for the given app. + * + * @param string $appId + * @return string + * @throws AppPathNotFoundException if app path can't be found + */ + public function getAppWebPath(string $appId): string { + $appWebPath = \OC_App::getAppWebPath($appId); + if ($appWebPath === false) { + throw new AppPathNotFoundException('Could not find web path for ' . $appId); + } + return $appWebPath; + } + + /** + * Clear the cached list of apps when enabling/disabling an app + */ + public function clearAppsCache() { + $settingsMemCache = $this->memCacheFactory->createDistributed('settings'); + $settingsMemCache->clear('listApps'); + $this->appInfos = []; + } + + /** + * Returns a list of apps that need upgrade + * + * @param string $version Nextcloud version as array of version components + * @return array list of app info from apps that need an upgrade + * + * @internal + */ + public function getAppsNeedingUpgrade($version) { + $appsToUpgrade = []; + $apps = $this->getInstalledApps(); + foreach ($apps as $appId) { + $appInfo = $this->getAppInfo($appId); + $appDbVersion = $this->appConfig->getValue($appId, 'installed_version'); + if ($appDbVersion + && isset($appInfo['version']) + && version_compare($appInfo['version'], $appDbVersion, '>') + && \OC_App::isAppCompatible($version, $appInfo) + ) { + $appsToUpgrade[] = $appInfo; + } + } + + return $appsToUpgrade; + } + + /** + * Returns the app information from "appinfo/info.xml". + * + * @param string $appId app id + * + * @param bool $path + * @param null $lang + * @return array|null app info + */ + public function getAppInfo(string $appId, bool $path = false, $lang = null) { + if ($path) { + $file = $appId; + } else { + if ($lang === null && isset($this->appInfos[$appId])) { + return $this->appInfos[$appId]; + } + try { + $appPath = $this->getAppPath($appId); + } catch (AppPathNotFoundException $e) { + return null; + } + $file = $appPath . '/appinfo/info.xml'; + } + + $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo')); + $data = $parser->parse($file); + + if (is_array($data)) { + $data = \OC_App::parseAppInfo($data, $lang); + } + + if ($lang === null) { + $this->appInfos[$appId] = $data; + } + + return $data; + } + + public function getAppVersion(string $appId, bool $useCache = true): string { + if (!$useCache || !isset($this->appVersions[$appId])) { + $appInfo = $this->getAppInfo($appId); + $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0'; + } + return $this->appVersions[$appId]; + } + + /** + * Returns a list of apps incompatible with the given version + * + * @param string $version Nextcloud version as array of version components + * + * @return array list of app info from incompatible apps + * + * @internal + */ + public function getIncompatibleApps(string $version): array { + $apps = $this->getInstalledApps(); + $incompatibleApps = []; + foreach ($apps as $appId) { + $info = $this->getAppInfo($appId); + if ($info === null) { + $incompatibleApps[] = ['id' => $appId]; + } elseif (!\OC_App::isAppCompatible($version, $info)) { + $incompatibleApps[] = $info; + } + } + return $incompatibleApps; + } + + /** + * @inheritdoc + * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped() + */ + public function isShipped($appId) { + $this->loadShippedJson(); + return in_array($appId, $this->shippedApps, true); + } + + private function isAlwaysEnabled($appId) { + $alwaysEnabled = $this->getAlwaysEnabledApps(); + return in_array($appId, $alwaysEnabled, true); + } + + /** + * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson() + * @throws \Exception + */ + private function loadShippedJson() { + if ($this->shippedApps === null) { + $shippedJson = \OC::$SERVERROOT . '/core/shipped.json'; + if (!file_exists($shippedJson)) { + throw new \Exception("File not found: $shippedJson"); + } + $content = json_decode(file_get_contents($shippedJson), true); + $this->shippedApps = $content['shippedApps']; + $this->alwaysEnabled = $content['alwaysEnabled']; + } + } + + /** + * @inheritdoc + */ + public function getAlwaysEnabledApps() { + $this->loadShippedJson(); + return $this->alwaysEnabled; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/Bundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/Bundle.php new file mode 100644 index 0000000..1143558 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/Bundle.php @@ -0,0 +1,62 @@ + + * + * @author Christoph Wurst + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +use OCP\IL10N; + +abstract class Bundle { + /** @var IL10N */ + protected $l10n; + + /** + * @param IL10N $l10n + */ + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * Get the identifier of the bundle + * + * @return string + */ + final public function getIdentifier() { + return substr(strrchr(get_class($this), '\\'), 1); + } + + /** + * Get the name of the bundle + * + * @return string + */ + abstract public function getName(); + + /** + * Get the list of app identifiers in the bundle + * + * @return array + */ + abstract public function getAppIdentifiers(); +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/BundleFetcher.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/BundleFetcher.php new file mode 100644 index 0000000..3ceb049 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/BundleFetcher.php @@ -0,0 +1,86 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +use OCP\IL10N; + +class BundleFetcher { + /** @var IL10N */ + private $l10n; + + /** + * @param IL10N $l10n + */ + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * @return Bundle[] + */ + public function getBundles() { + return [ + new EnterpriseBundle($this->l10n), + new HubBundle($this->l10n), + new GroupwareBundle($this->l10n), + new SocialSharingBundle($this->l10n), + new EducationBundle($this->l10n), + ]; + } + + /** + * Bundles that should be installed by default after installation + * + * @return Bundle[] + */ + public function getDefaultInstallationBundle() { + return [ + new CoreBundle($this->l10n), + ]; + } + + /** + * Get the bundle with the specified identifier + * + * @param string $identifier + * @return Bundle + * @throws \BadMethodCallException If the bundle does not exist + */ + public function getBundleByIdentifier($identifier) { + /** @var Bundle[] $bundles */ + $bundles = array_merge( + $this->getBundles(), + $this->getDefaultInstallationBundle() + ); + foreach ($bundles as $bundle) { + if ($bundle->getIdentifier() === $identifier) { + return $bundle; + } + } + + throw new \BadMethodCallException('Bundle with specified identifier does not exist'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/CoreBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/CoreBundle.php new file mode 100644 index 0000000..4e28d67 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/CoreBundle.php @@ -0,0 +1,43 @@ + + * + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class CoreBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return 'Core bundle'; + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'bruteforcesettings', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EducationBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EducationBundle.php new file mode 100644 index 0000000..05d6fbb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EducationBundle.php @@ -0,0 +1,48 @@ + + * + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class EducationBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Education Edition'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'dashboard', + 'circles', + 'groupfolders', + 'announcementcenter', + 'quota_warning', + 'user_saml', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EnterpriseBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EnterpriseBundle.php new file mode 100644 index 0000000..37213fe --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/EnterpriseBundle.php @@ -0,0 +1,50 @@ + + * + * @author Joas Schilling + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class EnterpriseBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName(): string { + return $this->l10n->t('Enterprise bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers(): array { + return [ + 'admin_audit', + 'user_ldap', + 'files_retention', + 'files_automatedtagging', + 'user_saml', + 'files_accesscontrol', + 'terms_of_service', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/GroupwareBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/GroupwareBundle.php new file mode 100644 index 0000000..49ae106 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/GroupwareBundle.php @@ -0,0 +1,47 @@ + + * + * @author Bjoern Schiessle + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class GroupwareBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Groupware bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'calendar', + 'contacts', + 'deck', + 'mail' + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/HubBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/HubBundle.php new file mode 100644 index 0000000..301be81 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/HubBundle.php @@ -0,0 +1,51 @@ + + * + * @author Arthur Schiwon + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class HubBundle extends Bundle { + public function getName() { + return $this->l10n->t('Hub bundle'); + } + + public function getAppIdentifiers() { + $hubApps = [ + 'spreed', + 'contacts', + 'calendar', + 'mail', + ]; + + $architecture = php_uname('m'); + if (PHP_OS_FAMILY === 'Linux' && in_array($architecture, ['x86_64', 'aarch64'])) { + $hubApps[] = 'richdocuments'; + $hubApps[] = 'richdocumentscode' . ($architecture === 'aarch64' ? '_arm64' : ''); + } + + return $hubApps; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/SocialSharingBundle.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/SocialSharingBundle.php new file mode 100644 index 0000000..8ce4d10 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Bundles/SocialSharingBundle.php @@ -0,0 +1,46 @@ + + * + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class SocialSharingBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Social sharing bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'socialsharing_twitter', + 'socialsharing_facebook', + 'socialsharing_email', + 'socialsharing_diaspora', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/AppFetcher.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/AppFetcher.php new file mode 100644 index 0000000..21ec38e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -0,0 +1,158 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Fetcher; + +use OC\App\AppStore\Version\VersionParser; +use OC\App\CompareVersion; +use OC\Files\AppData\Factory; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; + +class AppFetcher extends Fetcher { + + /** @var CompareVersion */ + private $compareVersion; + + /** @var bool */ + private $ignoreMaxVersion; + + /** + * @param Factory $appDataFactory + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + * @param IConfig $config + * @param CompareVersion $compareVersion + * @param ILogger $logger + */ + public function __construct(Factory $appDataFactory, + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + CompareVersion $compareVersion, + ILogger $logger) { + parent::__construct( + $appDataFactory, + $clientService, + $timeFactory, + $config, + $logger + ); + + $this->fileName = 'apps.json'; + $this->endpointName = 'apps.json'; + $this->compareVersion = $compareVersion; + $this->ignoreMaxVersion = true; + } + + /** + * Only returns the latest compatible app release in the releases array + * + * @param string $ETag + * @param string $content + * @param bool [$allowUnstable] Allow unstable releases + * + * @return array + */ + protected function fetch($ETag, $content, $allowUnstable = false) { + /** @var mixed[] $response */ + $response = parent::fetch($ETag, $content); + + $allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily'; + $allowNightly = $allowUnstable || $this->getChannel() === 'daily'; + + foreach ($response['data'] as $dataKey => $app) { + $releases = []; + + // Filter all compatible releases + foreach ($app['releases'] as $release) { + // Exclude all nightly and pre-releases if required + if (($allowNightly || $release['isNightly'] === false) + && ($allowPreReleases || strpos($release['version'], '-') === false)) { + // Exclude all versions not compatible with the current version + try { + $versionParser = new VersionParser(); + $version = $versionParser->getVersion($release['rawPlatformVersionSpec']); + $ncVersion = $this->getVersion(); + $min = $version->getMinimumVersion(); + $max = $version->getMaximumVersion(); + $minFulfilled = $this->compareVersion->isCompatible($ncVersion, $min, '>='); + $maxFulfilled = $max !== '' && + $this->compareVersion->isCompatible($ncVersion, $max, '<='); + if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled)) { + $releases[] = $release; + } + } catch (\InvalidArgumentException $e) { + $this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => ILogger::WARN]); + } + } + } + + if (empty($releases)) { + // Remove apps that don't have a matching release + $response['data'][$dataKey] = []; + continue; + } + + // Get the highest version + $versions = []; + foreach ($releases as $release) { + $versions[] = $release['version']; + } + usort($versions, 'version_compare'); + $versions = array_reverse($versions); + if (isset($versions[0])) { + $highestVersion = $versions[0]; + foreach ($releases as $release) { + if ((string)$release['version'] === (string)$highestVersion) { + $response['data'][$dataKey]['releases'] = [$release]; + break; + } + } + } + } + + $response['data'] = array_values(array_filter($response['data'])); + return $response; + } + + /** + * @param string $version + * @param string $fileName + * @param bool $ignoreMaxVersion + */ + public function setVersion(string $version, string $fileName = 'apps.json', bool $ignoreMaxVersion = true) { + parent::setVersion($version); + $this->fileName = $fileName; + $this->ignoreMaxVersion = $ignoreMaxVersion; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/CategoryFetcher.php new file mode 100644 index 0000000..e34fbee --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/CategoryFetcher.php @@ -0,0 +1,59 @@ + + * + * @author Georg Ehrke + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Fetcher; + +use OC\Files\AppData\Factory; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; + +class CategoryFetcher extends Fetcher { + /** + * @param Factory $appDataFactory + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + * @param IConfig $config + * @param ILogger $logger + */ + public function __construct(Factory $appDataFactory, + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + ILogger $logger) { + parent::__construct( + $appDataFactory, + $clientService, + $timeFactory, + $config, + $logger + ); + $this->fileName = 'categories.json'; + $this->endpointName = 'categories.json'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/Fetcher.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/Fetcher.php new file mode 100644 index 0000000..a844d9d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -0,0 +1,235 @@ + + * + * @author Daniel Kesselberg + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Steffen Lindner + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Fetcher; + +use GuzzleHttp\Exception\ConnectException; +use OC\Files\AppData\Factory; +use OCP\AppFramework\Http; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; + +abstract class Fetcher { + public const INVALIDATE_AFTER_SECONDS = 3600; + + /** @var IAppData */ + protected $appData; + /** @var IClientService */ + protected $clientService; + /** @var ITimeFactory */ + protected $timeFactory; + /** @var IConfig */ + protected $config; + /** @var Ilogger */ + protected $logger; + /** @var string */ + protected $fileName; + /** @var string */ + protected $endpointName; + /** @var string */ + protected $version; + /** @var string */ + protected $channel; + + /** + * @param Factory $appDataFactory + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + * @param IConfig $config + * @param ILogger $logger + */ + public function __construct(Factory $appDataFactory, + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + ILogger $logger) { + $this->appData = $appDataFactory->get('appstore'); + $this->clientService = $clientService; + $this->timeFactory = $timeFactory; + $this->config = $config; + $this->logger = $logger; + } + + /** + * Fetches the response from the server + * + * @param string $ETag + * @param string $content + * + * @return array + */ + protected function fetch($ETag, $content) { + $appstoreenabled = $this->config->getSystemValue('appstoreenabled', true); + + if (!$appstoreenabled) { + return []; + } + + $options = [ + 'timeout' => 60, + ]; + + if ($ETag !== '') { + $options['headers'] = [ + 'If-None-Match' => $ETag, + ]; + } + + $client = $this->clientService->newClient(); + $response = $client->get($this->getEndpoint(), $options); + + $responseJson = []; + if ($response->getStatusCode() === Http::STATUS_NOT_MODIFIED) { + $responseJson['data'] = json_decode($content, true); + } else { + $responseJson['data'] = json_decode($response->getBody(), true); + $ETag = $response->getHeader('ETag'); + } + + $responseJson['timestamp'] = $this->timeFactory->getTime(); + $responseJson['ncversion'] = $this->getVersion(); + if ($ETag !== '') { + $responseJson['ETag'] = $ETag; + } + + return $responseJson; + } + + /** + * Returns the array with the categories on the appstore server + * + * @param bool [$allowUnstable] Allow unstable releases + * @return array + */ + public function get($allowUnstable = false) { + $appstoreenabled = $this->config->getSystemValue('appstoreenabled', true); + $internetavailable = $this->config->getSystemValue('has_internet_connection', true); + + if (!$appstoreenabled || !$internetavailable) { + return []; + } + + $rootFolder = $this->appData->getFolder('/'); + + $ETag = ''; + $content = ''; + + try { + // File does already exists + $file = $rootFolder->getFile($this->fileName); + $jsonBlob = json_decode($file->getContent(), true); + + // Always get latests apps info if $allowUnstable + if (!$allowUnstable && is_array($jsonBlob)) { + + // No caching when the version has been updated + if (isset($jsonBlob['ncversion']) && $jsonBlob['ncversion'] === $this->getVersion()) { + + // If the timestamp is older than 3600 seconds request the files new + if ((int)$jsonBlob['timestamp'] > ($this->timeFactory->getTime() - self::INVALIDATE_AFTER_SECONDS)) { + return $jsonBlob['data']; + } + + if (isset($jsonBlob['ETag'])) { + $ETag = $jsonBlob['ETag']; + $content = json_encode($jsonBlob['data']); + } + } + } + } catch (NotFoundException $e) { + // File does not already exists + $file = $rootFolder->newFile($this->fileName); + } + + // Refresh the file content + try { + $responseJson = $this->fetch($ETag, $content, $allowUnstable); + // Don't store the apps request file + if ($allowUnstable) { + return $responseJson['data']; + } + + $file->putContent(json_encode($responseJson)); + return json_decode($file->getContent(), true)['data']; + } catch (ConnectException $e) { + $this->logger->warning('Could not connect to appstore: ' . $e->getMessage(), ['app' => 'appstoreFetcher']); + return []; + } catch (\Exception $e) { + $this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => ILogger::WARN]); + return []; + } + } + + /** + * Get the currently Nextcloud version + * @return string + */ + protected function getVersion() { + if ($this->version === null) { + $this->version = $this->config->getSystemValue('version', '0.0.0'); + } + return $this->version; + } + + /** + * Set the current Nextcloud version + * @param string $version + */ + public function setVersion(string $version) { + $this->version = $version; + } + + /** + * Get the currently Nextcloud update channel + * @return string + */ + protected function getChannel() { + if ($this->channel === null) { + $this->channel = \OC_Util::getChannel(); + } + return $this->channel; + } + + /** + * Set the current Nextcloud update channel + * @param string $channel + */ + public function setChannel(string $channel) { + $this->channel = $channel; + } + + protected function getEndpoint(): string { + return $this->config->getSystemValue('appstoreurl', 'https://apps.nextcloud.com/api/v1') . '/' . $this->endpointName; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/Version.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/Version.php new file mode 100644 index 0000000..ad43c73 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/Version.php @@ -0,0 +1,54 @@ + + * + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Version; + +class Version { + /** @var string */ + private $minVersion; + /** @var string */ + private $maxVersion; + + /** + * @param string $minVersion + * @param string $maxVersion + */ + public function __construct($minVersion, $maxVersion) { + $this->minVersion = $minVersion; + $this->maxVersion = $maxVersion; + } + + /** + * @return string + */ + public function getMinimumVersion() { + return $this->minVersion; + } + + /** + * @return string + */ + public function getMaximumVersion() { + return $this->maxVersion; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/VersionParser.php b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/VersionParser.php new file mode 100644 index 0000000..b2e2f16 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/AppStore/Version/VersionParser.php @@ -0,0 +1,86 @@ + + * + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Version; + +/** + * Class VersionParser parses the versions as sent by the Nextcloud app store + * + * @package OC\App\AppStore + */ +class VersionParser { + /** + * @param string $versionString + * @return bool + */ + private function isValidVersionString($versionString) { + return (bool)preg_match('/^[0-9.]+$/', $versionString); + } + + /** + * Returns the version for a version string + * + * @param string $versionSpec + * @return Version + * @throws \Exception If the version cannot be parsed + */ + public function getVersion($versionSpec) { + // * indicates that the version is compatible with all versions + if ($versionSpec === '*') { + return new Version('', ''); + } + + // Count the amount of =, if it is one then it's either maximum or minimum + // version. If it is two then it is maximum and minimum. + $versionElements = explode(' ', $versionSpec); + $firstVersion = isset($versionElements[0]) ? $versionElements[0] : ''; + $firstVersionNumber = substr($firstVersion, 2); + $secondVersion = isset($versionElements[1]) ? $versionElements[1] : ''; + $secondVersionNumber = substr($secondVersion, 2); + + switch (count($versionElements)) { + case 1: + if (!$this->isValidVersionString($firstVersionNumber)) { + break; + } + if (strpos($firstVersion, '>') === 0) { + return new Version($firstVersionNumber, ''); + } + return new Version('', $firstVersionNumber); + case 2: + if (!$this->isValidVersionString($firstVersionNumber) || !$this->isValidVersionString($secondVersionNumber)) { + break; + } + return new Version($firstVersionNumber, $secondVersionNumber); + } + + throw new \Exception( + sprintf( + 'Version cannot be parsed: %s', + $versionSpec + ) + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/AbstractCheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/AbstractCheck.php new file mode 100644 index 0000000..2e2209e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/AbstractCheck.php @@ -0,0 +1,140 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +abstract class AbstractCheck implements ICheck { + /** @var ICheck */ + protected $check; + + /** + * @param ICheck $check + */ + public function __construct(ICheck $check) { + $this->check = $check; + } + + /** + * @param int $errorCode + * @param string $errorObject + * @return string + */ + public function getDescription($errorCode, $errorObject) { + switch ($errorCode) { + case CodeChecker::STATIC_CALL_NOT_ALLOWED: + $functions = $this->getLocalFunctions(); + $functions = array_change_key_case($functions, CASE_LOWER); + if (isset($functions[$errorObject])) { + return $this->getLocalDescription(); + } + // no break; + case CodeChecker::CLASS_EXTENDS_NOT_ALLOWED: + case CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED: + case CodeChecker::CLASS_NEW_NOT_ALLOWED: + case CodeChecker::CLASS_USE_NOT_ALLOWED: + $classes = $this->getLocalClasses(); + $classes = array_change_key_case($classes, CASE_LOWER); + if (isset($classes[$errorObject])) { + return $this->getLocalDescription(); + } + break; + + case CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED: + $constants = $this->getLocalConstants(); + $constants = array_change_key_case($constants, CASE_LOWER); + if (isset($constants[$errorObject])) { + return $this->getLocalDescription(); + } + break; + + case CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED: + $methods = $this->getLocalMethods(); + $methods = array_change_key_case($methods, CASE_LOWER); + if (isset($methods[$errorObject])) { + return $this->getLocalDescription(); + } + break; + } + + return $this->check->getDescription($errorCode, $errorObject); + } + + /** + * @return string + */ + abstract protected function getLocalDescription(); + + /** + * @return array + */ + abstract protected function getLocalClasses(); + + /** + * @return array + */ + abstract protected function getLocalConstants(); + + /** + * @return array + */ + abstract protected function getLocalFunctions(); + + /** + * @return array + */ + abstract protected function getLocalMethods(); + + /** + * @return array E.g.: `'ClassName' => 'oc version',` + */ + public function getClasses() { + return array_merge($this->getLocalClasses(), $this->check->getClasses()); + } + + /** + * @return array E.g.: `'ClassName::CONSTANT_NAME' => 'oc version',` + */ + public function getConstants() { + return array_merge($this->getLocalConstants(), $this->check->getConstants()); + } + + /** + * @return array E.g.: `'functionName' => 'oc version',` + */ + public function getFunctions() { + return array_merge($this->getLocalFunctions(), $this->check->getFunctions()); + } + + /** + * @return array E.g.: `'ClassName::methodName' => 'oc version',` + */ + public function getMethods() { + return array_merge($this->getLocalMethods(), $this->check->getMethods()); + } + + /** + * @return bool + */ + public function checkStrongComparisons() { + return $this->check->checkStrongComparisons(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/CodeChecker.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/CodeChecker.php new file mode 100644 index 0000000..13d6ff8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/CodeChecker.php @@ -0,0 +1,140 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +use OC\Hooks\BasicEmitter; +use PhpParser\NodeTraverser; +use PhpParser\Parser; +use PhpParser\ParserFactory; +use RecursiveCallbackFilterIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RegexIterator; +use SplFileInfo; + +class CodeChecker extends BasicEmitter { + public const CLASS_EXTENDS_NOT_ALLOWED = 1000; + public const CLASS_IMPLEMENTS_NOT_ALLOWED = 1001; + public const STATIC_CALL_NOT_ALLOWED = 1002; + public const CLASS_CONST_FETCH_NOT_ALLOWED = 1003; + public const CLASS_NEW_NOT_ALLOWED = 1004; + public const OP_OPERATOR_USAGE_DISCOURAGED = 1005; + public const CLASS_USE_NOT_ALLOWED = 1006; + public const CLASS_METHOD_CALL_NOT_ALLOWED = 1007; + + /** @var Parser */ + private $parser; + + /** @var ICheck */ + protected $checkList; + + /** @var bool */ + protected $checkMigrationSchema; + + public function __construct(ICheck $checkList, $checkMigrationSchema) { + $this->checkList = $checkList; + $this->checkMigrationSchema = $checkMigrationSchema; + $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); + } + + /** + * @param string $appId + * @return array + * @throws \RuntimeException if app with $appId is unknown + */ + public function analyse(string $appId): array { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new \RuntimeException("No app with given id <$appId> known."); + } + + return $this->analyseFolder($appId, $appPath); + } + + /** + * @param string $appId + * @param string $folder + * @return array + */ + public function analyseFolder(string $appId, string $folder): array { + $errors = []; + + $excludedDirectories = ['vendor', '3rdparty', '.git', 'l10n', 'tests', 'test', 'build']; + if ($appId === 'password_policy') { + $excludedDirectories[] = 'lists'; + } + + $excludes = array_map(function ($item) use ($folder) { + return $folder . '/' . $item; + }, $excludedDirectories); + + $iterator = new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::SKIP_DOTS); + $iterator = new RecursiveCallbackFilterIterator($iterator, function ($item) use ($excludes) { + /** @var SplFileInfo $item */ + foreach ($excludes as $exclude) { + if (substr($item->getPath(), 0, strlen($exclude)) === $exclude) { + return false; + } + } + return true; + }); + $iterator = new RecursiveIteratorIterator($iterator); + $iterator = new RegexIterator($iterator, '/^.+\.php$/i'); + + foreach ($iterator as $file) { + /** @var SplFileInfo $file */ + $this->emit('CodeChecker', 'analyseFileBegin', [$file->getPathname()]); + $fileErrors = $this->analyseFile($file->__toString()); + $this->emit('CodeChecker', 'analyseFileFinished', [$file->getPathname(), $fileErrors]); + $errors = array_merge($fileErrors, $errors); + } + + return $errors; + } + + + /** + * @param string $file + * @return array + */ + public function analyseFile(string $file): array { + $code = file_get_contents($file); + $statements = $this->parser->parse($code); + + $visitor = new NodeVisitor($this->checkList); + $migrationVisitor = new MigrationSchemaChecker(); + $traverser = new NodeTraverser; + $traverser->addVisitor($visitor); + + if ($this->checkMigrationSchema && preg_match('#^.+\\/Migration\\/Version[^\\/]{1,255}\\.php$#i', $file)) { + $traverser->addVisitor($migrationVisitor); + } + + $traverser->traverse($statements); + + return array_merge($visitor->errors, $migrationVisitor->errors); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DatabaseSchemaChecker.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DatabaseSchemaChecker.php new file mode 100644 index 0000000..595a3fe --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DatabaseSchemaChecker.php @@ -0,0 +1,106 @@ + + * + * @author Joas Schilling + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\CodeChecker; + +class DatabaseSchemaChecker { + + /** + * @param string $appId + * @return array + */ + public function analyse($appId) { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new \RuntimeException("No app with given id <$appId> known."); + } + + if (!file_exists($appPath . '/appinfo/database.xml')) { + return ['errors' => [], 'warnings' => []]; + } + + libxml_use_internal_errors(true); + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($appPath . '/appinfo/database.xml'); + libxml_disable_entity_loader($loadEntities); + + + $errors = $warnings = []; + + foreach ($xml->table as $table) { + // Table names + if (strpos((string)$table->name, '*dbprefix*') !== 0) { + $errors[] = 'Database schema error: name of table ' . (string)$table->name . ' does not start with *dbprefix*'; + } + $tableName = substr((string)$table->name, strlen('*dbprefix*')); + if (strpos($tableName, '*dbprefix*') !== false) { + $warnings[] = 'Database schema warning: *dbprefix* should only appear once in name of table ' . (string)$table->name; + } + + if (strlen($tableName) > 27) { + $errors[] = 'Database schema error: Name of table ' . (string)$table->name . ' is too long (' . strlen($tableName) . '), max. 27 characters (21 characters for tables with autoincrement) + *dbprefix* allowed'; + } + + $hasAutoIncrement = false; + + // Column names + foreach ($table->declaration->field as $column) { + if (strpos((string)$column->name, '*dbprefix*') !== false) { + $warnings[] = 'Database schema warning: *dbprefix* should not appear in name of column ' . (string)$column->name . ' on table ' . (string)$table->name; + } + + if (strlen((string)$column->name) > 30) { + $errors[] = 'Database schema error: Name of column ' . (string)$column->name . ' on table ' . (string)$table->name . ' is too long (' . strlen($tableName) . '), max. 30 characters allowed'; + } + + if ($column->autoincrement) { + if ($hasAutoIncrement) { + $errors[] = 'Database schema error: Table ' . (string)$table->name . ' has multiple autoincrement columns'; + } + + if (strlen($tableName) > 21) { + $errors[] = 'Database schema error: Name of table ' . (string)$table->name . ' is too long (' . strlen($tableName) . '), max. 27 characters (21 characters for tables with autoincrement) + *dbprefix* allowed'; + } + + $hasAutoIncrement = true; + } + } + + // Index names + foreach ($table->declaration->index as $index) { + $hasPrefix = strpos((string)$index->name, '*dbprefix*'); + if ($hasPrefix !== false && $hasPrefix !== 0) { + $warnings[] = 'Database schema warning: *dbprefix* should only appear at the beginning in name of index ' . (string)$index->name . ' on table ' . (string)$table->name; + } + + $indexName = $hasPrefix === 0 ? substr((string)$index->name, strlen('*dbprefix*')) : (string)$index->name; + if (strlen($indexName) > 27) { + $errors[] = 'Database schema error: Name of index ' . (string)$index->name . ' on table ' . (string)$table->name . ' is too long (' . strlen($tableName) . '), max. 27 characters + *dbprefix* allowed'; + } + } + } + + return ['errors' => $errors, 'warnings' => $warnings]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DeprecationCheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DeprecationCheck.php new file mode 100644 index 0000000..a469cd2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/DeprecationCheck.php @@ -0,0 +1,196 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +class DeprecationCheck extends AbstractCheck { + /** + * @return string + */ + protected function getLocalDescription() { + return 'deprecated'; + } + + /** + * @return array E.g.: `'ClassName' => 'oc version',` + */ + protected function getLocalClasses() { + return [ + 'OC_JSON' => '8.2.0', + + 'OCP\API' => '9.1.0', + 'OCP\Contacts' => '8.1.0', + 'OCP\DB' => '8.1.0', + 'OCP\JSON' => '8.1.0', + 'OCP\Response' => '8.1.0', + 'OCP\AppFramework\IApi' => '8.0.0', + 'OCP\User' => '13.0.0', + 'OCP\BackgroundJob' => '14.0.0', + 'OCP\App' => '14.0.0', + 'OCP\Files' => '14.0.0', + ]; + } + + /** + * @return array E.g.: `'ClassName::CONSTANT_NAME' => 'oc version',` + */ + protected function getLocalConstants() { + return [ + 'OCP\API::GUEST_AUTH' => '9.1.0', + 'OCP\API::USER_AUTH' => '9.1.0', + 'OCP\API::SUBADMIN_AUTH' => '9.1.0', + 'OCP\API::ADMIN_AUTH' => '9.1.0', + 'OCP\API::RESPOND_UNAUTHORISED' => '9.1.0', + 'OCP\API::RESPOND_SERVER_ERROR' => '9.1.0', + 'OCP\API::RESPOND_NOT_FOUND' => '9.1.0', + 'OCP\API::RESPOND_UNKNOWN_ERROR' => '9.1.0', + + 'OC_API::GUEST_AUTH' => '8.2.0', + 'OC_API::USER_AUTH' => '8.2.0', + 'OC_API::SUBADMIN_AUTH' => '8.2.0', + 'OC_API::ADMIN_AUTH' => '8.2.0', + 'OC_API::RESPOND_UNAUTHORISED' => '8.2.0', + 'OC_API::RESPOND_SERVER_ERROR' => '8.2.0', + 'OC_API::RESPOND_NOT_FOUND' => '8.2.0', + 'OC_API::RESPOND_UNKNOWN_ERROR' => '8.2.0', + + 'OCP::PERMISSION_CREATE' => '8.0.0', + 'OCP::PERMISSION_READ' => '8.0.0', + 'OCP::PERMISSION_UPDATE' => '8.0.0', + 'OCP::PERMISSION_DELETE' => '8.0.0', + 'OCP::PERMISSION_SHARE' => '8.0.0', + 'OCP::PERMISSION_ALL' => '8.0.0', + 'OCP::FILENAME_INVALID_CHARS' => '8.0.0', + ]; + } + + /** + * @return array E.g.: `'functionName' => 'oc version',` + */ + protected function getLocalFunctions() { + return [ + 'OCP::image_path' => '8.0.0', + 'OCP::mimetype_icon' => '8.0.0', + 'OCP::preview_icon' => '8.0.0', + 'OCP::publicPreview_icon' => '8.0.0', + 'OCP::human_file_size' => '8.0.0', + 'OCP::relative_modified_date' => '8.0.0', + 'OCP::simple_file_size' => '8.0.0', + 'OCP::html_select_options' => '8.0.0', + ]; + } + + /** + * @return array E.g.: `'ClassName::methodName' => 'oc version',` + */ + protected function getLocalMethods() { + return [ + 'OC_L10N::get' => '8.2.0', + + 'OCP\Activity\IManager::publishActivity' => '8.2.0', + + 'OCP\App::register' => '8.1.0', + 'OCP\App::addNavigationEntry' => '8.1.0', + 'OCP\App::getActiveNavigationEntry' => '8.2.0', + 'OCP\App::setActiveNavigationEntry' => '8.1.0', + 'OCP\App::registerPersonal' => '14.0.0', + 'OCP\App::registerAdmin' => '14.0.0', + 'OC_App::getAppInfo' => '14.0.0', + 'OCP\App::getAppInfo' => '14.0.0', + 'OC_App::getAppVersion' => '14.0.0', + 'OCP\App::getAppVersion' => '14.0.0', + + 'OCP\AppFramework\Controller::params' => '7.0.0', + 'OCP\AppFramework\Controller::getParams' => '7.0.0', + 'OCP\AppFramework\Controller::method' => '7.0.0', + 'OCP\AppFramework\Controller::getUploadedFile' => '7.0.0', + 'OCP\AppFramework\Controller::env' => '7.0.0', + 'OCP\AppFramework\Controller::cookie' => '7.0.0', + 'OCP\AppFramework\Controller::render' => '7.0.0', + + 'OCP\AppFramework\IAppContainer::getCoreApi' => '8.0.0', + 'OCP\AppFramework\IAppContainer::isLoggedIn' => '8.0.0', + 'OCP\AppFramework\IAppContainer::isAdminUser' => '8.0.0', + 'OCP\AppFramework\IAppContainer::log' => '8.0.0', + + 'OCP\BackgroundJob::registerJob' => '8.1.0', + 'OCP\BackgroundJob::getExecutionType' => '14.0.0', + 'OCP\BackgroundJob::setExecutionType' => '14.0.0', + + 'OCP\Files::tmpFile' => '8.1.0', + 'OCP\Files::tmpFolder' => '8.1.0', + + 'OCP\IAppConfig::getValue' => '8.0.0', + 'OCP\IAppConfig::deleteKey' => '8.0.0', + 'OCP\IAppConfig::getKeys' => '8.0.0', + 'OCP\IAppConfig::setValue' => '8.0.0', + 'OCP\IAppConfig::deleteApp' => '8.0.0', + + 'OCP\IDBConnection::createQueryBuilder' => '8.2.0', + 'OCP\IDBConnection::getExpressionBuilder' => '8.2.0', + + 'OCP\ISearch::search' => '8.0.0', + + 'OCP\IServerContainer::getCache' => '8.2.0', + 'OCP\IServerContainer::getDb' => '8.1.0', + 'OCP\IServerContainer::getHTTPHelper' => '8.1.0', + + 'OCP\Response::disableCaching' => '14.0.0', + + 'OCP\User::getUser' => '8.0.0', + 'OCP\User::getUsers' => '8.1.0', + 'OCP\User::getDisplayName' => '8.1.0', + 'OCP\User::getDisplayNames' => '8.1.0', + 'OCP\User::userExists' => '8.1.0', + 'OCP\User::logout' => '8.1.0', + 'OCP\User::checkPassword' => '8.1.0', + 'OCP\User::isLoggedIn' => '13.0.0', + 'OCP\User::checkAdminUser' => '13.0.0', + 'OCP\User::checkLoggedIn' => '13.0.0', + + 'OCP\Util::encryptedFiles' => '8.1.0', + 'OCP\Util::formatDate' => '8.0.0', + 'OCP\Util::generateRandomBytes' => '8.1.0', + 'OCP\Util::getServerHost' => '8.1.0', + 'OCP\Util::getServerProtocol' => '8.1.0', + 'OCP\Util::getRequestUri' => '8.1.0', + 'OCP\Util::getScriptName' => '8.1.0', + 'OCP\Util::imagePath' => '8.1.0', + 'OCP\Util::isValidFileName' => '8.1.0', + 'OCP\Util::linkToRoute' => '8.1.0', + 'OCP\Util::linkTo' => '8.1.0', + 'OCP\Util::logException' => '8.2.0', + 'OCP\Util::mb_str_replace' => '8.2.0', + 'OCP\Util::mb_substr_replace' => '8.2.0', + 'OCP\Util::sendMail' => '8.1.0', + 'OCP\Util::writeLog' => '13.0.0', + + 'OCP\Files::rmdirr' => '14.0.0', + 'OCP\Files::getMimeType' => '14.0.0', + 'OCP\Files::searchByMime' => '14.0.0', + 'OCP\Files::streamCopy' => '14.0.0', + 'OCP\Files::buildNotExistingFileName' => '14.0.0', + 'OCP\Files::getStorage' => '14.0.0', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/EmptyCheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/EmptyCheck.php new file mode 100644 index 0000000..96fd5f9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/EmptyCheck.php @@ -0,0 +1,70 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +class EmptyCheck implements ICheck { + /** + * @param int $errorCode + * @param string $errorObject + * @return string + */ + public function getDescription($errorCode, $errorObject) { + return ''; + } + + /** + * @return array E.g.: `'ClassName' => 'oc version',` + */ + public function getClasses() { + return []; + } + + /** + * @return array E.g.: `'ClassName::CONSTANT_NAME' => 'oc version',` + */ + public function getConstants() { + return []; + } + + /** + * @return array E.g.: `'functionName' => 'oc version',` + */ + public function getFunctions() { + return []; + } + + /** + * @return array E.g.: `'ClassName::methodName' => 'oc version',` + */ + public function getMethods() { + return []; + } + + /** + * @return bool + */ + public function checkStrongComparisons() { + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/ICheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/ICheck.php new file mode 100644 index 0000000..5def46d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/ICheck.php @@ -0,0 +1,58 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +interface ICheck { + /** + * @param int $errorCode + * @param string $errorObject + * @return string + */ + public function getDescription($errorCode, $errorObject); + + /** + * @return array E.g.: `'ClassName' => 'oc version',` + */ + public function getClasses(); + + /** + * @return array E.g.: `'ClassName::CONSTANT_NAME' => 'oc version',` + */ + public function getConstants(); + + /** + * @return array E.g.: `'functionName' => 'oc version',` + */ + public function getFunctions(); + + /** + * @return array E.g.: `'ClassName::methodName' => 'oc version',` + */ + public function getMethods(); + + /** + * @return bool + */ + public function checkStrongComparisons(); +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/InfoChecker.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/InfoChecker.php new file mode 100644 index 0000000..038fd34 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/InfoChecker.php @@ -0,0 +1,108 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +use OC\Hooks\BasicEmitter; + +class InfoChecker extends BasicEmitter { + + /** @var string[] */ + private $shippedApps; + + /** @var string[] */ + private $alwaysEnabled; + + /** + * @param string $appId + * @return array + * @throws \RuntimeException + */ + public function analyse($appId): array { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new \RuntimeException("No app with given id <$appId> known."); + } + + $xml = new \DOMDocument(); + $xml->load($appPath . '/appinfo/info.xml'); + + $schema = \OC::$SERVERROOT . '/resources/app-info.xsd'; + try { + if ($this->isShipped($appId)) { + // Shipped apps are allowed to have the public and default_enabled tags + $schema = \OC::$SERVERROOT . '/resources/app-info-shipped.xsd'; + } + } catch (\Exception $e) { + // Assume it is not shipped + } + + $errors = []; + if (!$xml->schemaValidate($schema)) { + foreach (libxml_get_errors() as $error) { + $errors[] = [ + 'type' => 'parseError', + 'field' => $error->message, + ]; + $this->emit('InfoChecker', 'parseError', [$error->message]); + } + } + + return $errors; + } + + /** + * This is a copy of \OC\App\AppManager::isShipped(), keep both in sync. + * This method is copied, so the code checker works even when Nextcloud is + * not installed yet. The AppManager requires a database connection, which + * fails in that case. + * + * @param string $appId + * @return bool + * @throws \Exception + */ + protected function isShipped(string $appId): bool { + $this->loadShippedJson(); + return \in_array($appId, $this->shippedApps, true); + } + + /** + * This is a copy of \OC\App\AppManager::loadShippedJson(), keep both in sync + * This method is copied, so the code checker works even when Nextcloud is + * not installed yet. The AppManager requires a database connection, which + * fails in that case. + * + * @throws \Exception + */ + protected function loadShippedJson() { + if ($this->shippedApps === null) { + $shippedJson = \OC::$SERVERROOT . '/core/shipped.json'; + if (!file_exists($shippedJson)) { + throw new \Exception("File not found: $shippedJson"); + } + $content = json_decode(file_get_contents($shippedJson), true); + $this->shippedApps = $content['shippedApps']; + $this->alwaysEnabled = $content['alwaysEnabled']; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/LanguageParseChecker.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/LanguageParseChecker.php new file mode 100644 index 0000000..6c274bb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/LanguageParseChecker.php @@ -0,0 +1,60 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\CodeChecker; + +class LanguageParseChecker { + + /** + * @param string $appId + * @return array + */ + public function analyse($appId) { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new \RuntimeException("No app with given id <$appId> known."); + } + + if (!is_dir($appPath . '/l10n/')) { + return []; + } + + $errors = []; + $directory = new \DirectoryIterator($appPath . '/l10n/'); + + foreach ($directory as $file) { + if ($file->getExtension() !== 'json') { + continue; + } + + $content = file_get_contents($file->getPathname()); + json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + $errors[] = 'Invalid language file found: l10n/' . $file->getFilename() . ': ' . json_last_error_msg(); + } + } + + return $errors; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/MigrationSchemaChecker.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/MigrationSchemaChecker.php new file mode 100644 index 0000000..859daf2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/MigrationSchemaChecker.php @@ -0,0 +1,206 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\CodeChecker; + +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\NodeVisitorAbstract; + +class MigrationSchemaChecker extends NodeVisitorAbstract { + + /** @var string */ + protected $schemaVariableName = null; + /** @var array */ + protected $tableVariableNames = []; + /** @var array */ + public $errors = []; + + /** + * @param Node $node + * @return void + * + * @suppress PhanUndeclaredProperty + */ + public function enterNode(Node $node) { + /** + * Check tables + */ + if ($this->schemaVariableName !== null && + $node instanceof Node\Expr\Assign && + $node->var instanceof Node\Expr\Variable && + $node->expr instanceof Node\Expr\MethodCall && + $node->expr->var instanceof Node\Expr\Variable && + $node->expr->var->name === $this->schemaVariableName) { + if ($node->expr->name === 'createTable') { + if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) { + if (!$this->checkNameLength($node->expr->args[0]->value->value)) { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => $node->expr->args[0]->value->value, + 'reason' => 'Table name is too long (max. 27)', + ]; + } else { + $this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value; + } + } + } elseif ($node->expr->name === 'getTable') { + if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) { + $this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value; + } + } + } elseif ($this->schemaVariableName !== null && + $node instanceof Node\Expr\MethodCall && + $node->var instanceof Node\Expr\Variable && + $node->var->name === $this->schemaVariableName) { + if ($node->name === 'renameTable') { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => 'Deprecated method', + 'reason' => sprintf( + '`$%s->renameTable()` must not be used', + $node->var->name + ), + ]; + } + + /** + * Check columns and Indexes + */ + } elseif (!empty($this->tableVariableNames) && + $node instanceof Node\Expr\MethodCall && + $node->var instanceof Node\Expr\Variable && + isset($this->tableVariableNames[$node->var->name])) { + if ($node->name === 'addColumn' || $node->name === 'changeColumn') { + if (isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_) { + if (!$this->checkNameLength($node->args[0]->value->value)) { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => $node->args[0]->value->value, + 'reason' => sprintf( + 'Column name is too long on table `%s` (max. 27)', + $this->tableVariableNames[$node->var->name] + ), + ]; + } + + // On autoincrement the max length of the table name is 21 instead of 27 + if (isset($node->args[2]) && $node->args[2]->value instanceof Node\Expr\Array_) { + /** @var Node\Expr\Array_ $options */ + $options = $node->args[2]->value; + if ($this->checkColumnForAutoincrement($options)) { + if (!$this->checkNameLength($this->tableVariableNames[$node->var->name], true)) { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => $this->tableVariableNames[$node->var->name], + 'reason' => 'Table name is too long because of autoincrement (max. 21)', + ]; + } + } + } + } + } elseif ($node->name === 'addIndex' || + $node->name === 'addUniqueIndex' || + $node->name === 'renameIndex' || + $node->name === 'setPrimaryKey') { + if (isset($node->args[1]) && $node->args[1]->value instanceof Node\Scalar\String_) { + if (!$this->checkNameLength($node->args[1]->value->value)) { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => $node->args[1]->value->value, + 'reason' => sprintf( + 'Index name is too long on table `%s` (max. 27)', + $this->tableVariableNames[$node->var->name] + ), + ]; + } + } + } elseif ($node->name === 'addForeignKeyConstraint') { + if (isset($node->args[4]) && $node->args[4]->value instanceof Node\Scalar\String_) { + if (!$this->checkNameLength($node->args[4]->value->value)) { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => $node->args[4]->value->value, + 'reason' => sprintf( + 'Constraint name is too long on table `%s` (max. 27)', + $this->tableVariableNames[$node->var->name] + ), + ]; + } + } + } elseif ($node->name === 'renameColumn') { + $this->errors[] = [ + 'line' => $node->getLine(), + 'disallowedToken' => 'Deprecated method', + 'reason' => sprintf( + '`$%s->renameColumn()` must not be used', + $node->var->name + ), + ]; + } + + /** + * Find the schema + */ + } elseif ($node instanceof Node\Expr\Assign && + $node->expr instanceof Node\Expr\FuncCall && + $node->var instanceof Node\Expr\Variable && + $node->expr->name instanceof Node\Expr\Variable && + $node->expr->name->name === 'schemaClosure') { + // E.g. $schema = $schemaClosure(); + $this->schemaVariableName = $node->var->name; + } + } + + protected function checkNameLength($tableName, $hasAutoincrement = false) { + if ($hasAutoincrement) { + return strlen($tableName) <= 21; + } + return strlen($tableName) <= 27; + } + + /** + * @param Node\Expr\Array_ $optionsArray + * @return bool Whether the column is an autoincrement column + */ + protected function checkColumnForAutoincrement(Node\Expr\Array_ $optionsArray) { + foreach ($optionsArray->items as $option) { + if ($option->key instanceof Node\Scalar\String_) { + if ($option->key->value === 'autoincrement' && + $option->value instanceof Node\Expr\ConstFetch) { + /** @var Node\Expr\ConstFetch $const */ + $const = $option->value; + + if ($const->name instanceof Name && + $const->name->parts === ['true']) { + return true; + } + } + } + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/NodeVisitor.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/NodeVisitor.php new file mode 100644 index 0000000..635f135 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/NodeVisitor.php @@ -0,0 +1,309 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\NodeVisitorAbstract; + +class NodeVisitor extends NodeVisitorAbstract { + /** @var ICheck */ + protected $list; + + /** @var string */ + protected $blackListDescription; + /** @var string[] */ + protected $blackListedClassNames; + /** @var string[] */ + protected $blackListedConstants; + /** @var string[] */ + protected $blackListedFunctions; + /** @var string[] */ + protected $blackListedMethods; + /** @var bool */ + protected $checkEqualOperatorUsage; + /** @var string[] */ + protected $errorMessages; + + /** + * @param ICheck $list + */ + public function __construct(ICheck $list) { + $this->list = $list; + + $this->blackListedClassNames = []; + foreach ($list->getClasses() as $class => $blackListInfo) { + if (is_numeric($class) && is_string($blackListInfo)) { + $class = $blackListInfo; + $blackListInfo = null; + } + + $class = strtolower($class); + $this->blackListedClassNames[$class] = $class; + } + + $this->blackListedConstants = []; + foreach ($list->getConstants() as $constantName => $blackListInfo) { + $constantName = strtolower($constantName); + $this->blackListedConstants[$constantName] = $constantName; + } + + $this->blackListedFunctions = []; + foreach ($list->getFunctions() as $functionName => $blackListInfo) { + $functionName = strtolower($functionName); + $this->blackListedFunctions[$functionName] = $functionName; + } + + $this->blackListedMethods = []; + foreach ($list->getMethods() as $functionName => $blackListInfo) { + $functionName = strtolower($functionName); + $this->blackListedMethods[$functionName] = $functionName; + } + + $this->checkEqualOperatorUsage = $list->checkStrongComparisons(); + + $this->errorMessages = [ + CodeChecker::CLASS_EXTENDS_NOT_ALLOWED => "%s class must not be extended", + CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED => "%s interface must not be implemented", + CodeChecker::STATIC_CALL_NOT_ALLOWED => "Static method of %s class must not be called", + CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED => "Constant of %s class must not not be fetched", + CodeChecker::CLASS_NEW_NOT_ALLOWED => "%s class must not be instantiated", + CodeChecker::CLASS_USE_NOT_ALLOWED => "%s class must not be imported with a use statement", + CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED => "Method of %s class must not be called", + + CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED => "is discouraged", + ]; + } + + /** @var array */ + public $errors = []; + + public function enterNode(Node $node) { + if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\Equal) { + $this->errors[]= [ + 'disallowedToken' => '==', + 'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED, + 'line' => $node->getLine(), + 'reason' => $this->buildReason('==', CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED) + ]; + } + if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\NotEqual) { + $this->errors[]= [ + 'disallowedToken' => '!=', + 'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED, + 'line' => $node->getLine(), + 'reason' => $this->buildReason('!=', CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED) + ]; + } + if ($node instanceof Node\Stmt\Class_) { + if (!is_null($node->extends)) { + $this->checkBlackList($node->extends->toString(), CodeChecker::CLASS_EXTENDS_NOT_ALLOWED, $node); + } + foreach ($node->implements as $implements) { + $this->checkBlackList($implements->toString(), CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED, $node); + } + } + if ($node instanceof Node\Expr\StaticCall) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::STATIC_CALL_NOT_ALLOWED, $node); + + $this->checkBlackListFunction($node->class->toString(), $node->name, $node); + $this->checkBlackListMethod($node->class->toString(), $node->name, $node); + } + + if ($node->class instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = $c::call(); + */ + // $this->checkBlackListMethod($node->class->..., $node->name, $node); + } + } + } + if ($node instanceof Node\Expr\MethodCall) { + if (!is_null($node->var)) { + if ($node->var instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = new OC_API(); + * $n = $c::call(); + * $n = $c->call(); + */ + // $this->checkBlackListMethod($node->var->..., $node->name, $node); + } + } + } + if ($node instanceof Node\Expr\ClassConstFetch) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED, $node); + } + if ($node->class instanceof Node\Expr\Variable || $node->class instanceof Node\Expr\PropertyFetch) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = $i::ADMIN_AUTH; + */ + } else { + $this->checkBlackListConstant($node->class->toString(), $node->name, $node); + } + } + } + if ($node instanceof Node\Expr\New_) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::CLASS_NEW_NOT_ALLOWED, $node); + } + if ($node->class instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = new $i; + */ + } + } + } + if ($node instanceof Node\Stmt\UseUse) { + $this->checkBlackList($node->name->toString(), CodeChecker::CLASS_USE_NOT_ALLOWED, $node); + if ($node->alias) { + $this->addUseNameToBlackList($node->name->toString(), $node->alias); + } else { + $this->addUseNameToBlackList($node->name->toString(), $node->name->getLast()); + } + } + } + + /** + * Check whether an alias was introduced for a namespace of a blacklisted class + * + * Example: + * - Blacklist entry: OCP\AppFramework\IApi + * - Name: OCP\AppFramework + * - Alias: OAF + * => new blacklist entry: OAF\IApi + * + * @param string $name + * @param string $alias + */ + private function addUseNameToBlackList($name, $alias) { + $name = strtolower($name); + $alias = strtolower($alias); + + foreach ($this->blackListedClassNames as $blackListedAlias => $blackListedClassName) { + if (strpos($blackListedClassName, $name . '\\') === 0) { + $aliasedClassName = str_replace($name, $alias, $blackListedClassName); + $this->blackListedClassNames[$aliasedClassName] = $blackListedClassName; + } + } + + foreach ($this->blackListedConstants as $blackListedAlias => $blackListedConstant) { + if (strpos($blackListedConstant, $name . '\\') === 0 || strpos($blackListedConstant, $name . '::') === 0) { + $aliasedConstantName = str_replace($name, $alias, $blackListedConstant); + $this->blackListedConstants[$aliasedConstantName] = $blackListedConstant; + } + } + + foreach ($this->blackListedFunctions as $blackListedAlias => $blackListedFunction) { + if (strpos($blackListedFunction, $name . '\\') === 0 || strpos($blackListedFunction, $name . '::') === 0) { + $aliasedFunctionName = str_replace($name, $alias, $blackListedFunction); + $this->blackListedFunctions[$aliasedFunctionName] = $blackListedFunction; + } + } + + foreach ($this->blackListedMethods as $blackListedAlias => $blackListedMethod) { + if (strpos($blackListedMethod, $name . '\\') === 0 || strpos($blackListedMethod, $name . '::') === 0) { + $aliasedMethodName = str_replace($name, $alias, $blackListedMethod); + $this->blackListedMethods[$aliasedMethodName] = $blackListedMethod; + } + } + } + + private function checkBlackList($name, $errorCode, Node $node) { + $lowerName = strtolower($name); + + if (isset($this->blackListedClassNames[$lowerName])) { + $this->errors[]= [ + 'disallowedToken' => $name, + 'errorCode' => $errorCode, + 'line' => $node->getLine(), + 'reason' => $this->buildReason($this->blackListedClassNames[$lowerName], $errorCode) + ]; + } + } + + private function checkBlackListConstant($class, $constantName, Node $node) { + $name = $class . '::' . $constantName; + $lowerName = strtolower($name); + + if (isset($this->blackListedConstants[$lowerName])) { + $this->errors[]= [ + 'disallowedToken' => $name, + 'errorCode' => CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED, + 'line' => $node->getLine(), + 'reason' => $this->buildReason($this->blackListedConstants[$lowerName], CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED) + ]; + } + } + + private function checkBlackListFunction($class, $functionName, Node $node) { + $name = $class . '::' . $functionName; + $lowerName = strtolower($name); + + if (isset($this->blackListedFunctions[$lowerName])) { + $this->errors[]= [ + 'disallowedToken' => $name, + 'errorCode' => CodeChecker::STATIC_CALL_NOT_ALLOWED, + 'line' => $node->getLine(), + 'reason' => $this->buildReason($this->blackListedFunctions[$lowerName], CodeChecker::STATIC_CALL_NOT_ALLOWED) + ]; + } + } + + private function checkBlackListMethod($class, $functionName, Node $node) { + $name = $class . '::' . $functionName; + $lowerName = strtolower($name); + + if (isset($this->blackListedMethods[$lowerName])) { + $this->errors[]= [ + 'disallowedToken' => $name, + 'errorCode' => CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED, + 'line' => $node->getLine(), + 'reason' => $this->buildReason($this->blackListedMethods[$lowerName], CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED) + ]; + } + } + + private function buildReason($name, $errorCode) { + if (isset($this->errorMessages[$errorCode])) { + $desc = $this->list->getDescription($errorCode, $name); + return sprintf($this->errorMessages[$errorCode], $desc); + } + + return "$name usage not allowed - error: $errorCode"; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/PrivateCheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/PrivateCheck.php new file mode 100644 index 0000000..6ee4861 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/PrivateCheck.php @@ -0,0 +1,86 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +class PrivateCheck extends AbstractCheck { + /** + * @return string + */ + protected function getLocalDescription() { + return 'private'; + } + + /** + * @return array + */ + public function getLocalClasses() { + return [ + // classes replaced by the public api + 'OC_API' => '6.0.0', + 'OC_App' => '6.0.0', + 'OC_AppConfig' => '6.0.0', + 'OC_Avatar' => '6.0.0', + 'OC_BackgroundJob' => '6.0.0', + 'OC_Config' => '6.0.0', + 'OC_DB' => '6.0.0', + 'OC_Files' => '6.0.0', + 'OC_Helper' => '6.0.0', + 'OC_Hook' => '6.0.0', + 'OC_Image' => '6.0.0', + 'OC_JSON' => '6.0.0', + 'OC_L10N' => '6.0.0', + 'OC_Log' => '6.0.0', + 'OC_Mail' => '6.0.0', + 'OC_Preferences' => '6.0.0', + 'OC_Search_Provider' => '6.0.0', + 'OC_Search_Result' => '6.0.0', + 'OC_Request' => '6.0.0', + 'OC_Response' => '6.0.0', + 'OC_Template' => '6.0.0', + 'OC_User' => '6.0.0', + 'OC_Util' => '6.0.0', + ]; + } + + /** + * @return array + */ + public function getLocalConstants() { + return []; + } + + /** + * @return array + */ + public function getLocalFunctions() { + return []; + } + + /** + * @return array + */ + public function getLocalMethods() { + return []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/StrongComparisonCheck.php b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/StrongComparisonCheck.php new file mode 100644 index 0000000..89366f7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CodeChecker/StrongComparisonCheck.php @@ -0,0 +1,79 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App\CodeChecker; + +class StrongComparisonCheck implements ICheck { + /** @var ICheck */ + protected $check; + + /** + * @param ICheck $check + */ + public function __construct(ICheck $check) { + $this->check = $check; + } + + /** + * @param int $errorCode + * @param string $errorObject + * @return string + */ + public function getDescription($errorCode, $errorObject) { + return $this->check->getDescription($errorCode, $errorObject); + } + + /** + * @return array + */ + public function getClasses() { + return $this->check->getClasses(); + } + + /** + * @return array + */ + public function getConstants() { + return $this->check->getConstants(); + } + + /** + * @return array + */ + public function getFunctions() { + return $this->check->getFunctions(); + } + + /** + * @return array + */ + public function getMethods() { + return $this->check->getMethods(); + } + + /** + * @return bool + */ + public function checkStrongComparisons() { + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/CompareVersion.php b/docker/overlays/nextcloud/html/lib/private/App/CompareVersion.php new file mode 100644 index 0000000..5d38d4c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/CompareVersion.php @@ -0,0 +1,93 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App; + +use InvalidArgumentException; + +class CompareVersion { + public const REGEX_MAJOR = '/^\d+$/'; + public const REGEX_MAJOR_MINOR = '/^\d+.\d+$/'; + public const REGEX_MAJOR_MINOR_PATCH = '/^\d+.\d+.\d+$/'; + public const REGEX_SERVER = '/^\d+.\d+.\d+(.\d+)?$/'; + + /** + * Checks if the given server version fulfills the given (app) version requirements. + * + * Version requirements can be 'major.minor.patch', 'major.minor' or just 'major', + * so '13.0.1', '13.0' and '13' are valid. + * + * @param string $actual version as major.minor.patch notation + * @param string $required version where major is requried and minor and patch are optional + * @param string $comparator passed to `version_compare` + * @return bool whether the requirement is fulfilled + * @throws InvalidArgumentException if versions specified in an invalid format + */ + public function isCompatible(string $actual, string $required, + string $comparator = '>='): bool { + if (!preg_match(self::REGEX_SERVER, $actual)) { + throw new InvalidArgumentException('server version is invalid'); + } + + if (preg_match(self::REGEX_MAJOR, $required) === 1) { + return $this->compareMajor($actual, $required, $comparator); + } elseif (preg_match(self::REGEX_MAJOR_MINOR, $required) === 1) { + return $this->compareMajorMinor($actual, $required, $comparator); + } elseif (preg_match(self::REGEX_MAJOR_MINOR_PATCH, $required) === 1) { + return $this->compareMajorMinorPatch($actual, $required, $comparator); + } else { + throw new InvalidArgumentException('required version is invalid'); + } + } + + private function compareMajor(string $actual, string $required, + string $comparator) { + $actualMajor = explode('.', $actual)[0]; + $requiredMajor = explode('.', $required)[0]; + + return version_compare($actualMajor, $requiredMajor, $comparator); + } + + private function compareMajorMinor(string $actual, string $required, + string $comparator) { + $actualMajor = explode('.', $actual)[0]; + $actualMinor = explode('.', $actual)[1]; + $requiredMajor = explode('.', $required)[0]; + $requiredMinor = explode('.', $required)[1]; + + return version_compare("$actualMajor.$actualMinor", + "$requiredMajor.$requiredMinor", $comparator); + } + + private function compareMajorMinorPatch($actual, $required, $comparator) { + $actualMajor = explode('.', $actual)[0]; + $actualMinor = explode('.', $actual)[1]; + $actualPatch = explode('.', $actual)[2]; + $requiredMajor = explode('.', $required)[0]; + $requiredMinor = explode('.', $required)[1]; + $requiredPatch = explode('.', $required)[2]; + + return version_compare("$actualMajor.$actualMinor.$actualPatch", + "$requiredMajor.$requiredMinor.$requiredPatch", $comparator); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/DependencyAnalyzer.php b/docker/overlays/nextcloud/html/lib/private/App/DependencyAnalyzer.php new file mode 100644 index 0000000..f63cb38 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/DependencyAnalyzer.php @@ -0,0 +1,405 @@ + + * + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Thomas Müller + * @author Valdnet <47037905+Valdnet@users.noreply.github.com> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App; + +use OCP\IL10N; + +class DependencyAnalyzer { + + /** @var Platform */ + private $platform; + /** @var \OCP\IL10N */ + private $l; + /** @var array */ + private $appInfo; + + /** + * @param Platform $platform + * @param \OCP\IL10N $l + */ + public function __construct(Platform $platform, IL10N $l) { + $this->platform = $platform; + $this->l = $l; + } + + /** + * @param array $app + * @returns array of missing dependencies + */ + public function analyze(array $app, bool $ignoreMax = false) { + $this->appInfo = $app; + if (isset($app['dependencies'])) { + $dependencies = $app['dependencies']; + } else { + $dependencies = []; + } + + return array_merge( + $this->analyzeArchitecture($dependencies), + $this->analyzePhpVersion($dependencies), + $this->analyzeDatabases($dependencies), + $this->analyzeCommands($dependencies), + $this->analyzeLibraries($dependencies), + $this->analyzeOS($dependencies), + $this->analyzeOC($dependencies, $app, $ignoreMax) + ); + } + + public function isMarkedCompatible(array $app): bool { + if (isset($app['dependencies'])) { + $dependencies = $app['dependencies']; + } else { + $dependencies = []; + } + + $maxVersion = $this->getMaxVersion($dependencies, $app); + if ($maxVersion === null) { + return true; + } + return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion); + } + + /** + * Truncates both versions to the lowest common version, e.g. + * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1, + * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1 + * @param string $first + * @param string $second + * @return string[] first element is the first version, second element is the + * second version + */ + private function normalizeVersions($first, $second) { + $first = explode('.', $first); + $second = explode('.', $second); + + // get both arrays to the same minimum size + $length = min(count($second), count($first)); + $first = array_slice($first, 0, $length); + $second = array_slice($second, 0, $length); + + return [implode('.', $first), implode('.', $second)]; + } + + /** + * Parameters will be normalized and then passed into version_compare + * in the same order they are specified in the method header + * @param string $first + * @param string $second + * @param string $operator + * @return bool result similar to version_compare + */ + private function compare($first, $second, $operator) { + // we can't normalize versions if one of the given parameters is not a + // version string but null. In case one parameter is null normalization + // will therefore be skipped + if ($first !== null && $second !== null) { + list($first, $second) = $this->normalizeVersions($first, $second); + } + + return version_compare($first, $second, $operator); + } + + /** + * Checks if a version is bigger than another version + * @param string $first + * @param string $second + * @return bool true if the first version is bigger than the second + */ + private function compareBigger($first, $second) { + return $this->compare($first, $second, '>'); + } + + /** + * Checks if a version is smaller than another version + * @param string $first + * @param string $second + * @return bool true if the first version is smaller than the second + */ + private function compareSmaller($first, $second) { + return $this->compare($first, $second, '<'); + } + + /** + * @param array $dependencies + * @return array + */ + private function analyzePhpVersion(array $dependencies) { + $missing = []; + if (isset($dependencies['php']['@attributes']['min-version'])) { + $minVersion = $dependencies['php']['@attributes']['min-version']; + if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) { + $missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]); + } + } + if (isset($dependencies['php']['@attributes']['max-version'])) { + $maxVersion = $dependencies['php']['@attributes']['max-version']; + if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) { + $missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]); + } + } + if (isset($dependencies['php']['@attributes']['min-int-size'])) { + $intSize = $dependencies['php']['@attributes']['min-int-size']; + if ($intSize > $this->platform->getIntSize()*8) { + $missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]); + } + } + return $missing; + } + + private function analyzeArchitecture(array $dependencies) { + $missing = []; + if (!isset($dependencies['architecture'])) { + return $missing; + } + + $supportedArchitectures = $dependencies['architecture']; + if (empty($supportedArchitectures)) { + return $missing; + } + if (!is_array($supportedArchitectures)) { + $supportedArchitectures = [$supportedArchitectures]; + } + $supportedArchitectures = array_map(function ($architecture) { + return $this->getValue($architecture); + }, $supportedArchitectures); + $currentArchitecture = $this->platform->getArchitecture(); + if (!in_array($currentArchitecture, $supportedArchitectures, true)) { + $missing[] = (string)$this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]); + } + return $missing; + } + + /** + * @param array $dependencies + * @return array + */ + private function analyzeDatabases(array $dependencies) { + $missing = []; + if (!isset($dependencies['database'])) { + return $missing; + } + + $supportedDatabases = $dependencies['database']; + if (empty($supportedDatabases)) { + return $missing; + } + if (!is_array($supportedDatabases)) { + $supportedDatabases = [$supportedDatabases]; + } + $supportedDatabases = array_map(function ($db) { + return $this->getValue($db); + }, $supportedDatabases); + $currentDatabase = $this->platform->getDatabase(); + if (!in_array($currentDatabase, $supportedDatabases)) { + $missing[] = (string)$this->l->t('The following databases are supported: %s', [implode(', ', $supportedDatabases)]); + } + return $missing; + } + + /** + * @param array $dependencies + * @return array + */ + private function analyzeCommands(array $dependencies) { + $missing = []; + if (!isset($dependencies['command'])) { + return $missing; + } + + $commands = $dependencies['command']; + if (!is_array($commands)) { + $commands = [$commands]; + } + if (isset($commands['@value'])) { + $commands = [$commands]; + } + $os = $this->platform->getOS(); + foreach ($commands as $command) { + if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) { + continue; + } + $commandName = $this->getValue($command); + if (!$this->platform->isCommandKnown($commandName)) { + $missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]); + } + } + return $missing; + } + + /** + * @param array $dependencies + * @return array + */ + private function analyzeLibraries(array $dependencies) { + $missing = []; + if (!isset($dependencies['lib'])) { + return $missing; + } + + $libs = $dependencies['lib']; + if (!is_array($libs)) { + $libs = [$libs]; + } + if (isset($libs['@value'])) { + $libs = [$libs]; + } + foreach ($libs as $lib) { + $libName = $this->getValue($lib); + $libVersion = $this->platform->getLibraryVersion($libName); + if (is_null($libVersion)) { + $missing[] = $this->l->t('The library %s is not available.', [$libName]); + continue; + } + + if (is_array($lib)) { + if (isset($lib['@attributes']['min-version'])) { + $minVersion = $lib['@attributes']['min-version']; + if ($this->compareSmaller($libVersion, $minVersion)) { + $missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.', + [$libName, $minVersion, $libVersion]); + } + } + if (isset($lib['@attributes']['max-version'])) { + $maxVersion = $lib['@attributes']['max-version']; + if ($this->compareBigger($libVersion, $maxVersion)) { + $missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.', + [$libName, $maxVersion, $libVersion]); + } + } + } + } + return $missing; + } + + /** + * @param array $dependencies + * @return array + */ + private function analyzeOS(array $dependencies) { + $missing = []; + if (!isset($dependencies['os'])) { + return $missing; + } + + $oss = $dependencies['os']; + if (empty($oss)) { + return $missing; + } + if (is_array($oss)) { + $oss = array_map(function ($os) { + return $this->getValue($os); + }, $oss); + } else { + $oss = [$oss]; + } + $currentOS = $this->platform->getOS(); + if (!in_array($currentOS, $oss)) { + $missing[] = (string)$this->l->t('The following platforms are supported: %s', [implode(', ', $oss)]); + } + return $missing; + } + + /** + * @param array $dependencies + * @param array $appInfo + * @return array + */ + private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) { + $missing = []; + $minVersion = null; + if (isset($dependencies['nextcloud']['@attributes']['min-version'])) { + $minVersion = $dependencies['nextcloud']['@attributes']['min-version']; + } elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) { + $minVersion = $dependencies['owncloud']['@attributes']['min-version']; + } elseif (isset($appInfo['requiremin'])) { + $minVersion = $appInfo['requiremin']; + } elseif (isset($appInfo['require'])) { + $minVersion = $appInfo['require']; + } + $maxVersion = $this->getMaxVersion($dependencies, $appInfo); + + if (!is_null($minVersion)) { + if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) { + $missing[] = (string)$this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]); + } + } + if (!$ignoreMax && !is_null($maxVersion)) { + if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) { + $missing[] = (string)$this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]); + } + } + return $missing; + } + + private function getMaxVersion(array $dependencies, array $appInfo): ?string { + if (isset($dependencies['nextcloud']['@attributes']['max-version'])) { + return $dependencies['nextcloud']['@attributes']['max-version']; + } + if (isset($dependencies['owncloud']['@attributes']['max-version'])) { + return $dependencies['owncloud']['@attributes']['max-version']; + } + if (isset($appInfo['requiremax'])) { + return $appInfo['requiremax']; + } + + return null; + } + + /** + * Map the internal version number to the Nextcloud version + * + * @param string $version + * @return string + */ + protected function toVisibleVersion($version) { + switch ($version) { + case '9.1': + return '10'; + default: + if (strpos($version, '9.1.') === 0) { + $version = '10.0.' . substr($version, 4); + } + return $version; + } + } + + /** + * @param $element + * @return mixed + */ + private function getValue($element) { + if (isset($element['@value'])) { + return $element['@value']; + } + return (string)$element; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/InfoParser.php b/docker/overlays/nextcloud/html/lib/private/App/InfoParser.php new file mode 100644 index 0000000..6a56259 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/InfoParser.php @@ -0,0 +1,278 @@ + + * + * @author Andreas Fischer + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App; + +use OCP\ICache; + +class InfoParser { + /** @var \OCP\ICache|null */ + private $cache; + + /** + * @param ICache|null $cache + */ + public function __construct(ICache $cache = null) { + $this->cache = $cache; + } + + /** + * @param string $file the xml file to be loaded + * @return null|array where null is an indicator for an error + */ + public function parse($file) { + if (!file_exists($file)) { + return null; + } + + if ($this->cache !== null) { + $fileCacheKey = $file . filemtime($file); + if ($cachedValue = $this->cache->get($fileCacheKey)) { + return json_decode($cachedValue, true); + } + } + + libxml_use_internal_errors(true); + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($file); + + libxml_disable_entity_loader($loadEntities); + if ($xml === false) { + libxml_clear_errors(); + return null; + } + $array = $this->xmlToArray($xml); + + if (is_null($array)) { + return null; + } + + if (!array_key_exists('info', $array)) { + $array['info'] = []; + } + if (!array_key_exists('remote', $array)) { + $array['remote'] = []; + } + if (!array_key_exists('public', $array)) { + $array['public'] = []; + } + if (!array_key_exists('types', $array)) { + $array['types'] = []; + } + if (!array_key_exists('repair-steps', $array)) { + $array['repair-steps'] = []; + } + if (!array_key_exists('install', $array['repair-steps'])) { + $array['repair-steps']['install'] = []; + } + if (!array_key_exists('pre-migration', $array['repair-steps'])) { + $array['repair-steps']['pre-migration'] = []; + } + if (!array_key_exists('post-migration', $array['repair-steps'])) { + $array['repair-steps']['post-migration'] = []; + } + if (!array_key_exists('live-migration', $array['repair-steps'])) { + $array['repair-steps']['live-migration'] = []; + } + if (!array_key_exists('uninstall', $array['repair-steps'])) { + $array['repair-steps']['uninstall'] = []; + } + if (!array_key_exists('background-jobs', $array)) { + $array['background-jobs'] = []; + } + if (!array_key_exists('two-factor-providers', $array)) { + $array['two-factor-providers'] = []; + } + if (!array_key_exists('commands', $array)) { + $array['commands'] = []; + } + if (!array_key_exists('activity', $array)) { + $array['activity'] = []; + } + if (!array_key_exists('filters', $array['activity'])) { + $array['activity']['filters'] = []; + } + if (!array_key_exists('settings', $array['activity'])) { + $array['activity']['settings'] = []; + } + if (!array_key_exists('providers', $array['activity'])) { + $array['activity']['providers'] = []; + } + if (!array_key_exists('settings', $array)) { + $array['settings'] = []; + } + if (!array_key_exists('admin', $array['settings'])) { + $array['settings']['admin'] = []; + } + if (!array_key_exists('admin-section', $array['settings'])) { + $array['settings']['admin-section'] = []; + } + if (!array_key_exists('personal', $array['settings'])) { + $array['settings']['personal'] = []; + } + if (!array_key_exists('personal-section', $array['settings'])) { + $array['settings']['personal-section'] = []; + } + + if (array_key_exists('types', $array)) { + if (is_array($array['types'])) { + foreach ($array['types'] as $type => $v) { + unset($array['types'][$type]); + if (is_string($type)) { + $array['types'][] = $type; + } + } + } else { + $array['types'] = []; + } + } + if (isset($array['repair-steps']['install']['step']) && is_array($array['repair-steps']['install']['step'])) { + $array['repair-steps']['install'] = $array['repair-steps']['install']['step']; + } + if (isset($array['repair-steps']['pre-migration']['step']) && is_array($array['repair-steps']['pre-migration']['step'])) { + $array['repair-steps']['pre-migration'] = $array['repair-steps']['pre-migration']['step']; + } + if (isset($array['repair-steps']['post-migration']['step']) && is_array($array['repair-steps']['post-migration']['step'])) { + $array['repair-steps']['post-migration'] = $array['repair-steps']['post-migration']['step']; + } + if (isset($array['repair-steps']['live-migration']['step']) && is_array($array['repair-steps']['live-migration']['step'])) { + $array['repair-steps']['live-migration'] = $array['repair-steps']['live-migration']['step']; + } + if (isset($array['repair-steps']['uninstall']['step']) && is_array($array['repair-steps']['uninstall']['step'])) { + $array['repair-steps']['uninstall'] = $array['repair-steps']['uninstall']['step']; + } + if (isset($array['background-jobs']['job']) && is_array($array['background-jobs']['job'])) { + $array['background-jobs'] = $array['background-jobs']['job']; + } + if (isset($array['commands']['command']) && is_array($array['commands']['command'])) { + $array['commands'] = $array['commands']['command']; + } + if (isset($array['two-factor-providers']['provider']) && is_array($array['two-factor-providers']['provider'])) { + $array['two-factor-providers'] = $array['two-factor-providers']['provider']; + } + if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) { + $array['activity']['filters'] = $array['activity']['filters']['filter']; + } + if (isset($array['activity']['settings']['setting']) && is_array($array['activity']['settings']['setting'])) { + $array['activity']['settings'] = $array['activity']['settings']['setting']; + } + if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) { + $array['activity']['providers'] = $array['activity']['providers']['provider']; + } + if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']) + && is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']) + && !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class']) + ) { + $array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin']; + } + if (isset($array['settings']['admin']) && !is_array($array['settings']['admin'])) { + $array['settings']['admin'] = [$array['settings']['admin']]; + } + if (isset($array['settings']['admin-section']) && !is_array($array['settings']['admin-section'])) { + $array['settings']['admin-section'] = [$array['settings']['admin-section']]; + } + if (isset($array['settings']['personal']) && !is_array($array['settings']['personal'])) { + $array['settings']['personal'] = [$array['settings']['personal']]; + } + if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) { + $array['settings']['personal-section'] = [$array['settings']['personal-section']]; + } + + if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) { + $array['navigations']['navigation'] = [$array['navigations']['navigation']]; + } + + if ($this->cache !== null) { + $this->cache->set($fileCacheKey, json_encode($array)); + } + return $array; + } + + /** + * @param $data + * @return bool + */ + private function isNavigationItem($data): bool { + return isset($data['name'], $data['route']); + } + + /** + * @param \SimpleXMLElement $xml + * @return array + */ + public function xmlToArray($xml) { + if (!$xml->children()) { + return (string)$xml; + } + + $array = []; + foreach ($xml->children() as $element => $node) { + $totalElement = count($xml->{$element}); + + if (!isset($array[$element])) { + $array[$element] = $totalElement > 1 ? [] : ""; + } + /** @var \SimpleXMLElement $node */ + // Has attributes + if ($attributes = $node->attributes()) { + $data = [ + '@attributes' => [], + ]; + if (!count($node->children())) { + $value = (string)$node; + if (!empty($value)) { + $data['@value'] = (string)$node; + } + } else { + $data = array_merge($data, $this->xmlToArray($node)); + } + foreach ($attributes as $attr => $value) { + $data['@attributes'][$attr] = (string)$value; + } + + if ($totalElement > 1) { + $array[$element][] = $data; + } else { + $array[$element] = $data; + } + // Just a value + } else { + if ($totalElement > 1) { + $array[$element][] = $this->xmlToArray($node); + } else { + $array[$element] = $this->xmlToArray($node); + } + } + } + + return $array; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/Platform.php b/docker/overlays/nextcloud/html/lib/private/App/Platform.php new file mode 100644 index 0000000..03e9c7d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/Platform.php @@ -0,0 +1,104 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App; + +use OCP\IConfig; + +/** + * Class Platform + * + * This class basically abstracts any kind of information which can be retrieved from the underlying system. + * + * @package OC\App + */ +class Platform { + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * @return string + */ + public function getPhpVersion() { + return phpversion(); + } + + /** + * @return int + */ + public function getIntSize() { + return PHP_INT_SIZE; + } + + /** + * @return string + */ + public function getOcVersion() { + $v = \OCP\Util::getVersion(); + return implode('.', $v); + } + + /** + * @return string + */ + public function getDatabase() { + $dbType = $this->config->getSystemValue('dbtype', 'sqlite'); + if ($dbType === 'sqlite3') { + $dbType = 'sqlite'; + } + + return $dbType; + } + + /** + * @return string + */ + public function getOS() { + return php_uname('s'); + } + + /** + * @param $command + * @return bool + */ + public function isCommandKnown($command) { + $path = \OC_Helper::findBinaryPath($command); + return ($path !== null); + } + + public function getLibraryVersion($name) { + $repo = new PlatformRepository(); + return $repo->findLibrary($name); + } + + public function getArchitecture(): string { + return php_uname('m'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/App/PlatformRepository.php b/docker/overlays/nextcloud/html/lib/private/App/PlatformRepository.php new file mode 100644 index 0000000..816470e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/App/PlatformRepository.php @@ -0,0 +1,232 @@ + + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\App; + +/** + * Class PlatformRepository + * + * Inspired by the composer project - licensed under MIT + * https://github.com/composer/composer/blob/master/src/Composer/Repository/PlatformRepository.php#L82 + * + * @package OC\App + */ +class PlatformRepository { + public function __construct() { + $this->packages = $this->initialize(); + } + + protected function initialize() { + $loadedExtensions = get_loaded_extensions(); + $packages = []; + + // Extensions scanning + foreach ($loadedExtensions as $name) { + if (in_array($name, ['standard', 'Core'])) { + continue; + } + + $ext = new \ReflectionExtension($name); + try { + $prettyVersion = $ext->getVersion(); + $prettyVersion = $this->normalizeVersion($prettyVersion); + } catch (\UnexpectedValueException $e) { + $prettyVersion = '0'; + $prettyVersion = $this->normalizeVersion($prettyVersion); + } + + $packages[$this->buildPackageName($name)] = $prettyVersion; + } + + foreach ($loadedExtensions as $name) { + $prettyVersion = null; + switch ($name) { + case 'curl': + $curlVersion = curl_version(); + $prettyVersion = $curlVersion['version']; + break; + + case 'iconv': + $prettyVersion = ICONV_VERSION; + break; + + case 'intl': + $name = 'ICU'; + if (defined('INTL_ICU_VERSION')) { + $prettyVersion = INTL_ICU_VERSION; + } else { + $reflector = new \ReflectionExtension('intl'); + + ob_start(); + $reflector->info(); + $output = ob_get_clean(); + + preg_match('/^ICU version => (.*)$/m', $output, $matches); + $prettyVersion = $matches[1]; + } + + break; + + case 'libxml': + $prettyVersion = LIBXML_DOTTED_VERSION; + break; + + case 'openssl': + $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]?).*}', function ($match) { + return $match[1] . (empty($match[2]) ? '' : '.' . (ord($match[2]) - 96)); + }, OPENSSL_VERSION_TEXT); + break; + + case 'pcre': + $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION); + break; + + case 'uuid': + $prettyVersion = phpversion('uuid'); + break; + + case 'xsl': + $prettyVersion = LIBXSLT_DOTTED_VERSION; + break; + + default: + // None handled extensions have no special cases, skip + continue 2; + } + + try { + $prettyVersion = $this->normalizeVersion($prettyVersion); + } catch (\UnexpectedValueException $e) { + continue; + } + + $packages[$this->buildPackageName($name)] = $prettyVersion; + } + + return $packages; + } + + private function buildPackageName($name) { + return str_replace(' ', '-', $name); + } + + /** + * @param $name + * @return string + */ + public function findLibrary($name) { + $extName = $this->buildPackageName($name); + if (isset($this->packages[$extName])) { + return $this->packages[$extName]; + } + return null; + } + + private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'; + + /** + * Normalizes a version string to be able to perform comparisons on it + * + * https://github.com/composer/composer/blob/master/src/Composer/Package/Version/VersionParser.php#L94 + * + * @param string $version + * @param string $fullVersion optional complete version string to give more context + * @throws \UnexpectedValueException + * @return string + */ + public function normalizeVersion($version, $fullVersion = null) { + $version = trim($version); + if (null === $fullVersion) { + $fullVersion = $version; + } + // ignore aliases and just assume the alias is required instead of the source + if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) { + $version = $match[1]; + } + // match master-like branches + if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { + return '9999999-dev'; + } + if ('dev-' === strtolower(substr($version, 0, 4))) { + return 'dev-' . substr($version, 4); + } + // match classical versioning + if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = $matches[1] + . (!empty($matches[2]) ? $matches[2] : '.0') + . (!empty($matches[3]) ? $matches[3] : '.0') + . (!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; + } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { // match date-based versioning + $version = preg_replace('{\D}', '-', $matches[1]); + $index = 2; + } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = $matches[1] + . (!empty($matches[2]) ? $matches[2] : '.0') + . (!empty($matches[3]) ? $matches[3] : '.0') + . (!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; + } + // add version modifiers if a version was matched + if (isset($index)) { + if (!empty($matches[$index])) { + if ('stable' === $matches[$index]) { + return $version; + } + $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? $matches[$index + 1] : ''); + } + if (!empty($matches[$index + 2])) { + $version .= '-dev'; + } + return $version; + } + $extraMessage = ''; + if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; + } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; + } + throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage); + } + + /** + * @param string $stability + */ + private function expandStability($stability) { + $stability = strtolower($stability); + switch ($stability) { + case 'a': + return 'alpha'; + case 'b': + return 'beta'; + case 'p': + case 'pl': + return 'patch'; + case 'rc': + return 'RC'; + default: + return $stability; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppConfig.php b/docker/overlays/nextcloud/html/lib/private/AppConfig.php new file mode 100644 index 0000000..d3b6444 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppConfig.php @@ -0,0 +1,357 @@ + + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Arthur Schiwon + * @author Bart Visscher + * @author Christoph Wurst + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author michaelletzgus + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\DB\OracleConnection; +use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IDBConnection; + +/** + * This class provides an easy way for apps to store config values in the + * database. + */ +class AppConfig implements IAppConfig { + + /** @var array[] */ + protected $sensitiveValues = [ + 'external' => [ + '/^sites$/', + ], + 'spreed' => [ + '/^bridge_bot_password/', + '/^signaling_servers$/', + '/^signaling_ticket_secret$/', + '/^stun_servers$/', + '/^turn_servers$/', + '/^turn_server_secret$/', + ], + 'theming' => [ + '/^imprintUrl$/', + '/^privacyUrl$/', + '/^slogan$/', + '/^url$/', + ], + 'user_ldap' => [ + '/^(s..)?ldap_agent_password$/', + ], + ]; + + /** @var \OCP\IDBConnection */ + protected $conn; + + /** @var array[] */ + private $cache = []; + + /** @var bool */ + private $configLoaded = false; + + /** + * @param IDBConnection $conn + */ + public function __construct(IDBConnection $conn) { + $this->conn = $conn; + $this->configLoaded = false; + } + + /** + * @param string $app + * @return array + */ + private function getAppValues($app) { + $this->loadConfigValues(); + + if (isset($this->cache[$app])) { + return $this->cache[$app]; + } + + return []; + } + + /** + * Get all apps using the config + * + * @return array an array of app ids + * + * This function returns a list of all apps that have at least one + * entry in the appconfig table. + */ + public function getApps() { + $this->loadConfigValues(); + + return $this->getSortedKeys($this->cache); + } + + /** + * Get the available keys for an app + * + * @param string $app the app we are looking for + * @return array an array of key names + * + * This function gets all keys of an app. Please note that the values are + * not returned. + */ + public function getKeys($app) { + $this->loadConfigValues(); + + if (isset($this->cache[$app])) { + return $this->getSortedKeys($this->cache[$app]); + } + + return []; + } + + public function getSortedKeys($data) { + $keys = array_keys($data); + sort($keys); + return $keys; + } + + /** + * Gets the config value + * + * @param string $app app + * @param string $key key + * @param string $default = null, default value if the key does not exist + * @return string the value or $default + * + * This function gets a value from the appconfig table. If the key does + * not exist the default value will be returned + */ + public function getValue($app, $key, $default = null) { + $this->loadConfigValues(); + + if ($this->hasKey($app, $key)) { + return $this->cache[$app][$key]; + } + + return $default; + } + + /** + * check if a key is set in the appconfig + * + * @param string $app + * @param string $key + * @return bool + */ + public function hasKey($app, $key) { + $this->loadConfigValues(); + + return isset($this->cache[$app][$key]); + } + + /** + * Sets a value. If the key did not exist before it will be created. + * + * @param string $app app + * @param string $key key + * @param string|float|int $value value + * @return bool True if the value was inserted or updated, false if the value was the same + */ + public function setValue($app, $key, $value) { + if (!$this->hasKey($app, $key)) { + $inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [ + 'appid' => $app, + 'configkey' => $key, + 'configvalue' => $value, + ], [ + 'appid', + 'configkey', + ]); + + if ($inserted) { + if (!isset($this->cache[$app])) { + $this->cache[$app] = []; + } + + $this->cache[$app][$key] = $value; + return true; + } + } + + $sql = $this->conn->getQueryBuilder(); + $sql->update('appconfig') + ->set('configvalue', $sql->createParameter('configvalue')) + ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) + ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) + ->setParameter('configvalue', $value) + ->setParameter('app', $app) + ->setParameter('configkey', $key); + + /* + * Only limit to the existing value for non-Oracle DBs: + * http://docs.oracle.com/cd/E11882_01/server.112/e26088/conditions002.htm#i1033286 + * > Large objects (LOBs) are not supported in comparison conditions. + */ + if (!($this->conn instanceof OracleConnection)) { + // Only update the value when it is not the same + $sql->andWhere($sql->expr()->neq('configvalue', $sql->createParameter('configvalue'))) + ->setParameter('configvalue', $value); + } + + $changedRow = (bool) $sql->execute(); + + $this->cache[$app][$key] = $value; + + return $changedRow; + } + + /** + * Deletes a key + * + * @param string $app app + * @param string $key key + * @return boolean + */ + public function deleteKey($app, $key) { + $this->loadConfigValues(); + + $sql = $this->conn->getQueryBuilder(); + $sql->delete('appconfig') + ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) + ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) + ->setParameter('app', $app) + ->setParameter('configkey', $key); + $sql->execute(); + + unset($this->cache[$app][$key]); + return false; + } + + /** + * Remove app from appconfig + * + * @param string $app app + * @return boolean + * + * Removes all keys in appconfig belonging to the app. + */ + public function deleteApp($app) { + $this->loadConfigValues(); + + $sql = $this->conn->getQueryBuilder(); + $sql->delete('appconfig') + ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) + ->setParameter('app', $app); + $sql->execute(); + + unset($this->cache[$app]); + return false; + } + + /** + * get multiple values, either the app or key can be used as wildcard by setting it to false + * + * @param string|false $app + * @param string|false $key + * @return array|false + */ + public function getValues($app, $key) { + if (($app !== false) === ($key !== false)) { + return false; + } + + if ($key === false) { + return $this->getAppValues($app); + } else { + $appIds = $this->getApps(); + $values = array_map(function ($appId) use ($key) { + return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null; + }, $appIds); + $result = array_combine($appIds, $values); + + return array_filter($result); + } + } + + /** + * get all values of the app or and filters out sensitive data + * + * @param string $app + * @return array + */ + public function getFilteredValues($app) { + $values = $this->getValues($app, false); + + if (isset($this->sensitiveValues[$app])) { + foreach ($this->sensitiveValues[$app] as $sensitiveKeyExp) { + $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values)); + foreach ($sensitiveKeys as $sensitiveKey) { + $values[$sensitiveKey] = IConfig::SENSITIVE_VALUE; + } + } + } + + return $values; + } + + /** + * Load all the app config values + */ + protected function loadConfigValues() { + if ($this->configLoaded) { + return; + } + + $this->cache = []; + + $sql = $this->conn->getQueryBuilder(); + $sql->select('*') + ->from('appconfig'); + $result = $sql->execute(); + + // we are going to store the result in memory anyway + $rows = $result->fetchAll(); + foreach ($rows as $row) { + if (!isset($this->cache[$row['appid']])) { + $this->cache[$row['appid']] = []; + } + + $this->cache[$row['appid']][$row['configkey']] = $row['configvalue']; + } + $result->closeCursor(); + + $this->configLoaded = true; + } + + /** + * Clear all the cached app config values + * + * WARNING: do not use this - this is only for usage with the SCSSCacher to + * clear the memory cache of the app config + */ + public function clearCachedConfig() { + $this->configLoaded = false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/App.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/App.php new file mode 100644 index 0000000..563ef6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/App.php @@ -0,0 +1,229 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework; + +use OC\AppFramework\DependencyInjection\DIContainer; +use OC\AppFramework\Http\Dispatcher; +use OC\HintException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\ICallbackResponse; +use OCP\AppFramework\Http\IOutput; +use OCP\AppFramework\QueryException; +use OCP\IRequest; + +/** + * Entry point for every request in your app. You can consider this as your + * public static void main() method + * + * Handles all the dependency injection, controllers and output flow + */ +class App { + + /** @var string[] */ + private static $nameSpaceCache = []; + + /** + * Turns an app id into a namespace by either reading the appinfo.xml's + * namespace tag or uppercasing the appid's first letter + * @param string $appId the app id + * @param string $topNamespace the namespace which should be prepended to + * the transformed app id, defaults to OCA\ + * @return string the starting namespace for the app + */ + public static function buildAppNamespace(string $appId, string $topNamespace='OCA\\'): string { + // Hit the cache! + if (isset(self::$nameSpaceCache[$appId])) { + return $topNamespace . self::$nameSpaceCache[$appId]; + } + + $appInfo = \OC_App::getAppInfo($appId); + if (isset($appInfo['namespace'])) { + self::$nameSpaceCache[$appId] = trim($appInfo['namespace']); + } else { + if ($appId !== 'spreed') { + // if the tag is not found, fall back to uppercasing the first letter + self::$nameSpaceCache[$appId] = ucfirst($appId); + } else { + // For the Talk app (appid spreed) the above fallback doesn't work. + // This leads to a problem when trying to install it freshly, + // because the apps namespace is already registered before the + // app is downloaded from the appstore, because of the hackish + // global route index.php/call/{token} which is registered via + // the core/routes.php so it does not have the app namespace. + // @ref https://github.com/nextcloud/server/pull/19433 + self::$nameSpaceCache[$appId] = 'Talk'; + } + } + + return $topNamespace . self::$nameSpaceCache[$appId]; + } + + public static function getAppIdForClass(string $className, string $topNamespace='OCA\\'): ?string { + if (strpos($className, $topNamespace) !== 0) { + return null; + } + + foreach (self::$nameSpaceCache as $appId => $namespace) { + if (strpos($className, $topNamespace . $namespace . '\\') === 0) { + return $appId; + } + } + + return null; + } + + + /** + * Shortcut for calling a controller method and printing the result + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param DIContainer $container an instance of a pimple container. + * @param array $urlParams list of URL parameters (optional) + * @throws HintException + */ + public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) { + if (!is_null($urlParams)) { + $container->query(IRequest::class)->setUrlParameters($urlParams); + } elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) { + $container->query(IRequest::class)->setUrlParameters($container['urlParams']); + } + $appName = $container['AppName']; + + // first try $controllerName then go for \OCA\AppName\Controller\$controllerName + try { + $controller = $container->query($controllerName); + } catch (QueryException $e) { + if (strpos($controllerName, '\\Controller\\') !== false) { + // This is from a global registered app route that is not enabled. + [/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3); + throw new HintException('App ' . strtolower($app) . ' is not enabled'); + } + + if ($appName === 'core') { + $appNameSpace = 'OC\\Core'; + } else { + $appNameSpace = self::buildAppNamespace($appName); + } + $controllerName = $appNameSpace . '\\Controller\\' . $controllerName; + $controller = $container->query($controllerName); + } + + // initialize the dispatcher and run all the middleware before the controller + /** @var Dispatcher $dispatcher */ + $dispatcher = $container['Dispatcher']; + + list( + $httpHeaders, + $responseHeaders, + $responseCookies, + $output, + $response + ) = $dispatcher->dispatch($controller, $methodName); + + $io = $container[IOutput::class]; + + if (!is_null($httpHeaders)) { + $io->setHeader($httpHeaders); + } + + foreach ($responseHeaders as $name => $value) { + $io->setHeader($name . ': ' . $value); + } + + foreach ($responseCookies as $name => $value) { + $expireDate = null; + if ($value['expireDate'] instanceof \DateTime) { + $expireDate = $value['expireDate']->getTimestamp(); + } + $sameSite = $value['sameSite'] ?? 'Lax'; + + $io->setCookie( + $name, + $value['value'], + $expireDate, + $container->getServer()->getWebRoot(), + null, + $container->getServer()->getRequest()->getServerProtocol() === 'https', + true, + $sameSite + ); + } + + /* + * Status 204 does not have a body and no Content Length + * Status 304 does not have a body and does not need a Content Length + * https://tools.ietf.org/html/rfc7230#section-3.3 + * https://tools.ietf.org/html/rfc7230#section-3.3.2 + */ + $emptyResponse = false; + if (preg_match('/^HTTP\/\d\.\d (\d{3}) .*$/', $httpHeaders, $matches)) { + $status = (int)$matches[1]; + if ($status === Http::STATUS_NO_CONTENT || $status === Http::STATUS_NOT_MODIFIED) { + $emptyResponse = true; + } + } + + if (!$emptyResponse) { + if ($response instanceof ICallbackResponse) { + $response->callback($io); + } elseif (!is_null($output)) { + $io->setHeader('Content-Length: ' . strlen($output)); + $io->setOutput($output); + } + } + } + + /** + * Shortcut for calling a controller method and printing the result. + * Similar to App:main except that no headers will be sent. + * This should be used for example when registering sections via + * \OC\AppFramework\Core\API::registerAdmin() + * + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + * @param DIContainer $container an instance of a pimple container. + */ + public static function part(string $controllerName, string $methodName, array $urlParams, + DIContainer $container) { + $container['urlParams'] = $urlParams; + $controller = $container[$controllerName]; + + $dispatcher = $container['Dispatcher']; + + list(, , $output) = $dispatcher->dispatch($controller, $methodName); + return $output; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/BootContext.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/BootContext.php new file mode 100644 index 0000000..0acfaad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/BootContext.php @@ -0,0 +1,54 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Bootstrap; + +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\IAppContainer; +use OCP\IServerContainer; + +class BootContext implements IBootContext { + + /** @var IAppContainer */ + private $appContainer; + + public function __construct(IAppContainer $appContainer) { + $this->appContainer = $appContainer; + } + + public function getAppContainer(): IAppContainer { + return $this->appContainer; + } + + public function getServerContainer(): IServerContainer { + return $this->appContainer->get(IServerContainer::class); + } + + public function injectFn(callable $fn) { + return (new FunctionInjector($this->appContainer))->injectFn($fn); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/Coordinator.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/Coordinator.php new file mode 100644 index 0000000..c8155c3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -0,0 +1,188 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Bootstrap; + +use OC\Support\CrashReport\Registry; +use OC_App; +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\QueryException; +use OCP\Dashboard\IManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\ILogger; +use OCP\IServerContainer; +use RuntimeException; +use Throwable; +use function class_exists; +use function class_implements; +use function in_array; + +class Coordinator { + + /** @var IServerContainer */ + private $serverContainer; + + /** @var Registry */ + private $registry; + + /** @var IManager */ + private $dashboardManager; + + /** @var IEventDispatcher */ + private $eventDispatcher; + + /** @var ILogger */ + private $logger; + + /** @var RegistrationContext|null */ + private $registrationContext; + + /** @var string[] */ + private $bootedApps = []; + + public function __construct(IServerContainer $container, + Registry $registry, + IManager $dashboardManager, + IEventDispatcher $eventListener, + ILogger $logger) { + $this->serverContainer = $container; + $this->registry = $registry; + $this->dashboardManager = $dashboardManager; + $this->eventDispatcher = $eventListener; + $this->logger = $logger; + } + + public function runRegistration(): void { + if ($this->registrationContext !== null) { + throw new RuntimeException('Registration has already been run'); + } + + $this->registrationContext = new RegistrationContext($this->logger); + $apps = []; + foreach (OC_App::getEnabledApps() as $appId) { + /* + * First, we have to enable the app's autoloader + * + * @todo use $this->appManager->getAppPath($appId) here + */ + $path = OC_App::getAppPath($appId); + if ($path === false) { + // Ignore + continue; + } + OC_App::registerAutoloading($appId, $path); + + /* + * Next we check if there is an application class and it implements + * the \OCP\AppFramework\Bootstrap\IBootstrap interface + */ + $appNameSpace = App::buildAppNamespace($appId); + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + if (class_exists($applicationClassName) && in_array(IBootstrap::class, class_implements($applicationClassName), true)) { + try { + /** @var IBootstrap|App $application */ + $apps[$appId] = $application = $this->serverContainer->query($applicationClassName); + } catch (QueryException $e) { + // Weird, but ok + continue; + } + try { + $application->register($this->registrationContext->for($appId)); + } catch (Throwable $e) { + $this->logger->logException($e, [ + 'message' => 'Error during app service registration: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + } + } + + /** + * Now that all register methods have been called, we can delegate the registrations + * to the actual services + */ + $this->registrationContext->delegateCapabilityRegistrations($apps); + $this->registrationContext->delegateCrashReporterRegistrations($apps, $this->registry); + $this->registrationContext->delegateDashboardPanelRegistrations($apps, $this->dashboardManager); + $this->registrationContext->delegateEventListenerRegistrations($this->eventDispatcher); + $this->registrationContext->delegateContainerRegistrations($apps); + $this->registrationContext->delegateMiddlewareRegistrations($apps); + } + + public function getRegistrationContext(): ?RegistrationContext { + return $this->registrationContext; + } + + public function bootApp(string $appId): void { + if (isset($this->bootedApps[$appId])) { + return; + } + $this->bootedApps[$appId] = true; + + $appNameSpace = App::buildAppNamespace($appId); + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + if (!class_exists($applicationClassName)) { + // Nothing to boot + return; + } + + /* + * Now it is time to fetch an instance of the App class. For classes + * that implement \OCP\AppFramework\Bootstrap\IBootstrap this means + * the instance was already created for register, but any other + * (legacy) code will now do their magic via the constructor. + */ + try { + /** @var App $application */ + $application = $this->serverContainer->query($applicationClassName); + if ($application instanceof IBootstrap) { + /** @var BootContext $context */ + $context = new BootContext($application->getContainer()); + $application->boot($context); + } + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'message' => "Could not boot $appId" . $e->getMessage(), + ]); + } catch (Throwable $e) { + $this->logger->logException($e, [ + 'message' => "Could not boot $appId" . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + } + + public function isBootable(string $appId) { + $appNameSpace = App::buildAppNamespace($appId); + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + return class_exists($applicationClassName) && + in_array(IBootstrap::class, class_implements($applicationClassName), true); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/FunctionInjector.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/FunctionInjector.php new file mode 100644 index 0000000..3c20701 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/FunctionInjector.php @@ -0,0 +1,70 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Bootstrap; + +use Closure; +use OCP\AppFramework\QueryException; +use Psr\Container\ContainerInterface; +use ReflectionFunction; +use ReflectionParameter; +use function array_map; + +class FunctionInjector { + + /** @var ContainerInterface */ + private $container; + + public function __construct(ContainerInterface $container) { + $this->container = $container; + } + + public function injectFn(callable $fn) { + $reflected = new ReflectionFunction(Closure::fromCallable($fn)); + return $fn(...array_map(function (ReflectionParameter $param) { + // First we try by type (more likely these days) + if (($type = $param->getType()) !== null) { + try { + return $this->container->get($type->getName()); + } catch (QueryException $ex) { + // Ignore and try name as well + } + } + // Second we try by name (mostly for primitives) + try { + return $this->container->get($param->getName()); + } catch (QueryException $ex) { + // As a last resort we pass `null` if allowed + if ($type !== null && $type->allowsNull()) { + return null; + } + + // Nothing worked, time to bail out + throw $ex; + } + }, $reflected->getParameters())); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/RegistrationContext.php new file mode 100644 index 0000000..e6512a5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -0,0 +1,416 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Bootstrap; + +use Closure; +use OC\Support\CrashReport\Registry; +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Dashboard\IManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\ILogger; +use Throwable; + +class RegistrationContext { + + /** @var array[] */ + private $capabilities = []; + + /** @var array[] */ + private $crashReporters = []; + + /** @var array[] */ + private $dashboardPanels = []; + + /** @var array[] */ + private $services = []; + + /** @var array[] */ + private $aliases = []; + + /** @var array[] */ + private $parameters = []; + + /** @var array[] */ + private $eventListeners = []; + + /** @var array[] */ + private $middlewares = []; + + /** @var array[] */ + private $searchProviders = []; + + /** @var array[] */ + private $alternativeLogins = []; + + /** @var ILogger */ + private $logger; + + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + public function for(string $appId): IRegistrationContext { + return new class($appId, $this) implements IRegistrationContext { + /** @var string */ + private $appId; + + /** @var RegistrationContext */ + private $context; + + public function __construct(string $appId, RegistrationContext $context) { + $this->appId = $appId; + $this->context = $context; + } + + public function registerCapability(string $capability): void { + $this->context->registerCapability( + $this->appId, + $capability + ); + } + + public function registerCrashReporter(string $reporterClass): void { + $this->context->registerCrashReporter( + $this->appId, + $reporterClass + ); + } + + public function registerDashboardWidget(string $widgetClass): void { + $this->context->registerDashboardPanel( + $this->appId, + $widgetClass + ); + } + + public function registerService(string $name, callable $factory, bool $shared = true): void { + $this->context->registerService( + $this->appId, + $name, + $factory, + $shared + ); + } + + public function registerServiceAlias(string $alias, string $target): void { + $this->context->registerServiceAlias( + $this->appId, + $alias, + $target + ); + } + + public function registerParameter(string $name, $value): void { + $this->context->registerParameter( + $this->appId, + $name, + $value + ); + } + + public function registerEventListener(string $event, string $listener, int $priority = 0): void { + $this->context->registerEventListener( + $this->appId, + $event, + $listener, + $priority + ); + } + + public function registerMiddleware(string $class): void { + $this->context->registerMiddleware( + $this->appId, + $class + ); + } + + public function registerSearchProvider(string $class): void { + $this->context->registerSearchProvider( + $this->appId, + $class + ); + } + + public function registerAlternativeLogin(string $class): void { + $this->context->registerAlternativeLogin( + $this->appId, + $class + ); + } + }; + } + + public function registerCapability(string $appId, string $capability): void { + $this->capabilities[] = [ + 'appId' => $appId, + 'capability' => $capability + ]; + } + + public function registerCrashReporter(string $appId, string $reporterClass): void { + $this->crashReporters[] = [ + 'appId' => $appId, + 'class' => $reporterClass, + ]; + } + + public function registerDashboardPanel(string $appId, string $panelClass): void { + $this->dashboardPanels[] = [ + 'appId' => $appId, + 'class' => $panelClass + ]; + } + + public function registerService(string $appId, string $name, callable $factory, bool $shared = true): void { + $this->services[] = [ + "appId" => $appId, + "name" => $name, + "factory" => $factory, + "shared" => $shared, + ]; + } + + public function registerServiceAlias(string $appId, string $alias, string $target): void { + $this->aliases[] = [ + "appId" => $appId, + "alias" => $alias, + "target" => $target, + ]; + } + + public function registerParameter(string $appId, string $name, $value): void { + $this->parameters[] = [ + "appId" => $appId, + "name" => $name, + "value" => $value, + ]; + } + + public function registerEventListener(string $appId, string $event, string $listener, int $priority = 0): void { + $this->eventListeners[] = [ + "appId" => $appId, + "event" => $event, + "listener" => $listener, + "priority" => $priority, + ]; + } + + public function registerMiddleware(string $appId, string $class): void { + $this->middlewares[] = [ + "appId" => $appId, + "class" => $class, + ]; + } + + public function registerSearchProvider(string $appId, string $class) { + $this->searchProviders[] = [ + 'appId' => $appId, + 'class' => $class, + ]; + } + + public function registerAlternativeLogin(string $appId, string $class): void { + $this->alternativeLogins[] = [ + 'appId' => $appId, + 'class' => $class, + ]; + } + + /** + * @param App[] $apps + */ + public function delegateCapabilityRegistrations(array $apps): void { + foreach ($this->capabilities as $registration) { + try { + $apps[$registration['appId']] + ->getContainer() + ->registerCapability($registration['capability']); + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during capability registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * @param App[] $apps + */ + public function delegateCrashReporterRegistrations(array $apps, Registry $registry): void { + foreach ($this->crashReporters as $registration) { + try { + $registry->registerLazy($registration['class']); + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during crash reporter registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * @param App[] $apps + */ + public function delegateDashboardPanelRegistrations(array $apps, IManager $dashboardManager): void { + foreach ($this->dashboardPanels as $panel) { + try { + $dashboardManager->lazyRegisterWidget($panel['class']); + } catch (Throwable $e) { + $appId = $panel['appId']; + $this->logger->logException($e, [ + 'message' => "Error during dashboard registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + public function delegateEventListenerRegistrations(IEventDispatcher $eventDispatcher): void { + foreach ($this->eventListeners as $registration) { + try { + if (isset($registration['priority'])) { + $eventDispatcher->addServiceListener( + $registration['event'], + $registration['listener'], + $registration['priority'] + ); + } else { + $eventDispatcher->addServiceListener( + $registration['event'], + $registration['listener'] + ); + } + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during event listener registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * @param App[] $apps + */ + public function delegateContainerRegistrations(array $apps): void { + foreach ($this->services as $registration) { + try { + /** + * Register the service and convert the callable into a \Closure if necessary + */ + $apps[$registration['appId']] + ->getContainer() + ->registerService( + $registration['name'], + Closure::fromCallable($registration['factory']), + $registration['shared'] ?? true + ); + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during service registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + + foreach ($this->aliases as $registration) { + try { + $apps[$registration['appId']] + ->getContainer() + ->registerAlias( + $registration['alias'], + $registration['target'] + ); + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during service alias registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + + foreach ($this->parameters as $registration) { + try { + $apps[$registration['appId']] + ->getContainer() + ->registerParameter( + $registration['name'], + $registration['value'] + ); + } catch (Throwable $e) { + $appId = $registration['appId']; + $this->logger->logException($e, [ + 'message' => "Error during service alias registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * @param App[] $apps + */ + public function delegateMiddlewareRegistrations(array $apps): void { + foreach ($this->middlewares as $middleware) { + try { + $apps[$middleware['appId']] + ->getContainer() + ->registerMiddleWare($middleware['class']); + } catch (Throwable $e) { + $appId = $middleware['appId']; + $this->logger->logException($e, [ + 'message' => "Error during capability registration of $appId: " . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * @return array[] + */ + public function getSearchProviders(): array { + return $this->searchProviders; + } + + /** + * @return array[] + */ + public function getAlternativeLogins(): array { + return $this->alternativeLogins; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/DependencyInjection/DIContainer.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/DependencyInjection/DIContainer.php new file mode 100644 index 0000000..875597a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -0,0 +1,461 @@ + + * @author Bernhard Posselt + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sebastian Wessalowski + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\DependencyInjection; + +use OC; +use OC\AppFramework\Http; +use OC\AppFramework\Http\Dispatcher; +use OC\AppFramework\Http\Output; +use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Middleware\OCSMiddleware; +use OC\AppFramework\Middleware\Security\CORSMiddleware; +use OC\AppFramework\Middleware\Security\RateLimitingMiddleware; +use OC\AppFramework\Middleware\Security\SecurityMiddleware; +use OC\AppFramework\Middleware\SessionMiddleware; +use OC\AppFramework\ScopedPsrLogger; +use OC\AppFramework\Utility\SimpleContainer; +use OC\Core\Middleware\TwoFactorMiddleware; +use OC\Log\PsrLoggerAdapter; +use OC\ServerContainer; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\IOutput; +use OCP\AppFramework\IAppContainer; +use OCP\AppFramework\QueryException; +use OCP\AppFramework\Services\IAppConfig; +use OCP\AppFramework\Services\IInitialState; +use OCP\AppFramework\Utility\IControllerMethodReflector; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\Folder; +use OCP\Files\IAppData; +use OCP\Group\ISubAdmin; +use OCP\IConfig; +use OCP\IInitialStateService; +use OCP\IL10N; +use OCP\ILogger; +use OCP\INavigationManager; +use OCP\IRequest; +use OCP\IServerContainer; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUserSession; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * @deprecated 20.0.0 + */ +class DIContainer extends SimpleContainer implements IAppContainer { + + /** + * @var array + */ + private $middleWares = []; + + /** @var ServerContainer */ + private $server; + + /** + * Put your class dependencies in here + * @param string $appName the name of the app + * @param array $urlParams + * @param ServerContainer|null $server + */ + public function __construct($appName, $urlParams = [], ServerContainer $server = null) { + parent::__construct(); + $this['AppName'] = $appName; + $this['urlParams'] = $urlParams; + + $this->registerAlias('Request', IRequest::class); + + /** @var \OC\ServerContainer $server */ + if ($server === null) { + $server = \OC::$server; + } + $this->server = $server; + $this->server->registerAppContainer($appName, $this); + + // aliases + $this->registerAlias('appName', 'AppName'); + $this->registerAlias('webRoot', 'WebRoot'); + $this->registerAlias('userId', 'UserId'); + + /** + * Core services + */ + $this->registerService(IOutput::class, function () { + return new Output($this->getServer()->getWebRoot()); + }); + + $this->registerService(Folder::class, function () { + return $this->getServer()->getUserFolder(); + }); + + $this->registerService(IAppData::class, function (ContainerInterface $c) { + return $this->getServer()->getAppDataDir($c->get('AppName')); + }); + + $this->registerService(IL10N::class, function (ContainerInterface $c) { + return $this->getServer()->getL10N($c->get('AppName')); + }); + + // Log wrappers + $this->registerService(LoggerInterface::class, function (ContainerInterface $c) { + return new ScopedPsrLogger( + $c->get(PsrLoggerAdapter::class), + $c->get('AppName') + ); + }); + $this->registerService(ILogger::class, function (ContainerInterface $c) { + return new OC\AppFramework\Logger($this->server->query(ILogger::class), $c->get('AppName')); + }); + + $this->registerService(IServerContainer::class, function () { + return $this->getServer(); + }); + $this->registerAlias('ServerContainer', IServerContainer::class); + + $this->registerService(\OCP\WorkflowEngine\IManager::class, function (ContainerInterface $c) { + return $c->get(Manager::class); + }); + + $this->registerService(ContainerInterface::class, function (ContainerInterface $c) { + return $c; + }); + $this->registerAlias(IAppContainer::class, ContainerInterface::class); + + // commonly used attributes + $this->registerService('UserId', function (ContainerInterface $c) { + return $c->get(IUserSession::class)->getSession()->get('user_id'); + }); + + $this->registerService('WebRoot', function (ContainerInterface $c) { + return $c->get(IServerContainer::class)->getWebRoot(); + }); + + $this->registerService('OC_Defaults', function (ContainerInterface $c) { + return $c->get(IServerContainer::class)->getThemingDefaults(); + }); + + $this->registerService('Protocol', function (ContainerInterface $c) { + /** @var \OC\Server $server */ + $server = $c->get(IServerContainer::class); + $protocol = $server->getRequest()->getHttpProtocol(); + return new Http($_SERVER, $protocol); + }); + + $this->registerService('Dispatcher', function (ContainerInterface $c) { + return new Dispatcher( + $c->get('Protocol'), + $c->get(MiddlewareDispatcher::class), + $c->get(IControllerMethodReflector::class), + $c->get(IRequest::class) + ); + }); + + /** + * App Framework default arguments + */ + $this->registerParameter('corsMethods', 'PUT, POST, GET, DELETE, PATCH'); + $this->registerParameter('corsAllowedHeaders', 'Authorization, Content-Type, Accept'); + $this->registerParameter('corsMaxAge', 1728000); + + /** + * Middleware + */ + $this->registerAlias('MiddlewareDispatcher', MiddlewareDispatcher::class); + $this->registerService(MiddlewareDispatcher::class, function (ContainerInterface $c) { + $server = $this->getServer(); + + $dispatcher = new MiddlewareDispatcher(); + + $dispatcher->registerMiddleware( + $c->get(OC\AppFramework\Middleware\CompressionMiddleware::class) + ); + + $dispatcher->registerMiddleware($c->get(OC\AppFramework\Middleware\NotModifiedMiddleware::class)); + + $dispatcher->registerMiddleware( + $c->get(OC\AppFramework\Middleware\Security\ReloadExecutionMiddleware::class) + ); + + $dispatcher->registerMiddleware( + new OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware( + $c->get(IRequest::class), + $c->get(IControllerMethodReflector::class) + ) + ); + $dispatcher->registerMiddleware( + new CORSMiddleware( + $c->get(IRequest::class), + $c->get(IControllerMethodReflector::class), + $c->get(IUserSession::class), + $c->get(OC\Security\Bruteforce\Throttler::class) + ) + ); + $dispatcher->registerMiddleware( + new OCSMiddleware( + $c->get(IRequest::class) + ) + ); + + $securityMiddleware = new SecurityMiddleware( + $c->get(IRequest::class), + $c->get(IControllerMethodReflector::class), + $c->get(INavigationManager::class), + $c->get(IURLGenerator::class), + $server->query(ILogger::class), + $c->get('AppName'), + $server->getUserSession()->isLoggedIn(), + $server->getGroupManager()->isAdmin($this->getUserId()), + $server->getUserSession()->getUser() !== null && $server->query(ISubAdmin::class)->isSubAdmin($server->getUserSession()->getUser()), + $server->getAppManager(), + $server->getL10N('lib') + ); + $dispatcher->registerMiddleware($securityMiddleware); + $dispatcher->registerMiddleware( + new OC\AppFramework\Middleware\Security\CSPMiddleware( + $server->query(OC\Security\CSP\ContentSecurityPolicyManager::class), + $server->query(OC\Security\CSP\ContentSecurityPolicyNonceManager::class), + $server->query(OC\Security\CSRF\CsrfTokenManager::class) + ) + ); + $dispatcher->registerMiddleware( + $server->query(OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware::class) + ); + $dispatcher->registerMiddleware( + new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware( + $c->get(IControllerMethodReflector::class), + $c->get(ISession::class), + $c->get(IUserSession::class), + $c->get(ITimeFactory::class) + ) + ); + $dispatcher->registerMiddleware( + new TwoFactorMiddleware( + $c->get(OC\Authentication\TwoFactorAuth\Manager::class), + $c->get(IUserSession::class), + $c->get(ISession::class), + $c->get(IURLGenerator::class), + $c->get(IControllerMethodReflector::class), + $c->get(IRequest::class) + ) + ); + $dispatcher->registerMiddleware( + new OC\AppFramework\Middleware\Security\BruteForceMiddleware( + $c->get(IControllerMethodReflector::class), + $c->get(OC\Security\Bruteforce\Throttler::class), + $c->get(IRequest::class) + ) + ); + $dispatcher->registerMiddleware( + new RateLimitingMiddleware( + $c->get(IRequest::class), + $c->get(IUserSession::class), + $c->get(IControllerMethodReflector::class), + $c->get(OC\Security\RateLimiting\Limiter::class) + ) + ); + $dispatcher->registerMiddleware( + new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware( + $c->get(IRequest::class), + $c->get(ISession::class), + $c->get(\OCP\IConfig::class) + ) + ); + $dispatcher->registerMiddleware( + $c->get(\OC\AppFramework\Middleware\AdditionalScriptsMiddleware::class) + ); + + foreach ($this->middleWares as $middleWare) { + $dispatcher->registerMiddleware($c->get($middleWare)); + } + + $dispatcher->registerMiddleware( + new SessionMiddleware( + $c->get(IControllerMethodReflector::class), + $c->get(ISession::class) + ) + ); + return $dispatcher; + }); + + $this->registerService(IAppConfig::class, function (ContainerInterface $c) { + return new OC\AppFramework\Services\AppConfig( + $c->get(IConfig::class), + $c->get('AppName') + ); + }); + $this->registerService(IInitialState::class, function (ContainerInterface $c) { + return new OC\AppFramework\Services\InitialState( + $c->get(IInitialStateService::class), + $c->get('AppName') + ); + }); + } + + /** + * @return \OCP\IServerContainer + */ + public function getServer() { + return $this->server; + } + + /** + * @param string $middleWare + * @return boolean|null + */ + public function registerMiddleWare($middleWare) { + if (in_array($middleWare, $this->middleWares, true) !== false) { + return false; + } + $this->middleWares[] = $middleWare; + } + + /** + * used to return the appname of the set application + * @return string the name of your application + */ + public function getAppName() { + return $this->query('AppName'); + } + + /** + * @deprecated use IUserSession->isLoggedIn() + * @return boolean + */ + public function isLoggedIn() { + return \OC::$server->getUserSession()->isLoggedIn(); + } + + /** + * @deprecated use IGroupManager->isAdmin($userId) + * @return boolean + */ + public function isAdminUser() { + $uid = $this->getUserId(); + return \OC_User::isAdminUser($uid); + } + + private function getUserId() { + return $this->getServer()->getSession()->get('user_id'); + } + + /** + * @deprecated use the ILogger instead + * @param string $message + * @param string $level + * @return mixed + */ + public function log($message, $level) { + switch ($level) { + case 'debug': + $level = ILogger::DEBUG; + break; + case 'info': + $level = ILogger::INFO; + break; + case 'warn': + $level = ILogger::WARN; + break; + case 'fatal': + $level = ILogger::FATAL; + break; + default: + $level = ILogger::ERROR; + break; + } + \OCP\Util::writeLog($this->getAppName(), $message, $level); + } + + /** + * Register a capability + * + * @param string $serviceName e.g. 'OCA\Files\Capabilities' + */ + public function registerCapability($serviceName) { + $this->query('OC\CapabilitiesManager')->registerCapability(function () use ($serviceName) { + return $this->query($serviceName); + }); + } + + public function has($id): bool { + if (parent::has($id)) { + return true; + } + + if ($this->server->has($id, true)) { + return true; + } + + return false; + } + + public function query(string $name, bool $autoload = true) { + try { + return $this->queryNoFallback($name); + } catch (QueryException $firstException) { + try { + return $this->getServer()->query($name, $autoload); + } catch (QueryException $secondException) { + if ($firstException->getCode() === 1) { + throw $secondException; + } + throw $firstException; + } + } + } + + /** + * @param string $name + * @return mixed + * @throws QueryException if the query could not be resolved + */ + public function queryNoFallback($name) { + $name = $this->sanitizeName($name); + + if ($this->offsetExists($name)) { + return parent::query($name); + } elseif ($this['AppName'] === 'settings' && strpos($name, 'OC\\Settings\\') === 0) { + return parent::query($name); + } elseif ($this['AppName'] === 'core' && strpos($name, 'OC\\Core\\') === 0) { + return parent::query($name); + } elseif (strpos($name, \OC\AppFramework\App::buildAppNamespace($this['AppName']) . '\\') === 0) { + return parent::query($name); + } + + throw new QueryException('Could not resolve ' . $name . '!' . + ' Class can not be instantiated', 1); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Http.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http.php new file mode 100644 index 0000000..88e4981 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http.php @@ -0,0 +1,131 @@ + + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework; + +use OCP\AppFramework\Http as BaseHttp; + +class Http extends BaseHttp { + private $server; + private $protocolVersion; + protected $headers; + + /** + * @param array $server $_SERVER + * @param string $protocolVersion the http version to use defaults to HTTP/1.1 + */ + public function __construct($server, $protocolVersion='HTTP/1.1') { + $this->server = $server; + $this->protocolVersion = $protocolVersion; + + $this->headers = [ + self::STATUS_CONTINUE => 'Continue', + self::STATUS_SWITCHING_PROTOCOLS => 'Switching Protocols', + self::STATUS_PROCESSING => 'Processing', + self::STATUS_OK => 'OK', + self::STATUS_CREATED => 'Created', + self::STATUS_ACCEPTED => 'Accepted', + self::STATUS_NON_AUTHORATIVE_INFORMATION => 'Non-Authorative Information', + self::STATUS_NO_CONTENT => 'No Content', + self::STATUS_RESET_CONTENT => 'Reset Content', + self::STATUS_PARTIAL_CONTENT => 'Partial Content', + self::STATUS_MULTI_STATUS => 'Multi-Status', // RFC 4918 + self::STATUS_ALREADY_REPORTED => 'Already Reported', // RFC 5842 + self::STATUS_IM_USED => 'IM Used', // RFC 3229 + self::STATUS_MULTIPLE_CHOICES => 'Multiple Choices', + self::STATUS_MOVED_PERMANENTLY => 'Moved Permanently', + self::STATUS_FOUND => 'Found', + self::STATUS_SEE_OTHER => 'See Other', + self::STATUS_NOT_MODIFIED => 'Not Modified', + self::STATUS_USE_PROXY => 'Use Proxy', + self::STATUS_RESERVED => 'Reserved', + self::STATUS_TEMPORARY_REDIRECT => 'Temporary Redirect', + self::STATUS_BAD_REQUEST => 'Bad request', + self::STATUS_UNAUTHORIZED => 'Unauthorized', + self::STATUS_PAYMENT_REQUIRED => 'Payment Required', + self::STATUS_FORBIDDEN => 'Forbidden', + self::STATUS_NOT_FOUND => 'Not Found', + self::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', + self::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', + self::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + self::STATUS_REQUEST_TIMEOUT => 'Request Timeout', + self::STATUS_CONFLICT => 'Conflict', + self::STATUS_GONE => 'Gone', + self::STATUS_LENGTH_REQUIRED => 'Length Required', + self::STATUS_PRECONDITION_FAILED => 'Precondition failed', + self::STATUS_REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large', + self::STATUS_REQUEST_URI_TOO_LONG => 'Request-URI Too Long', + self::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + self::STATUS_REQUEST_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', + self::STATUS_EXPECTATION_FAILED => 'Expectation Failed', + self::STATUS_IM_A_TEAPOT => 'I\'m a teapot', // RFC 2324 + self::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', // RFC 4918 + self::STATUS_LOCKED => 'Locked', // RFC 4918 + self::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', // RFC 4918 + self::STATUS_UPGRADE_REQUIRED => 'Upgrade required', + self::STATUS_PRECONDITION_REQUIRED => 'Precondition required', // draft-nottingham-http-new-status + self::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', // draft-nottingham-http-new-status + self::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', // draft-nottingham-http-new-status + self::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error', + self::STATUS_NOT_IMPLEMENTED => 'Not Implemented', + self::STATUS_BAD_GATEWAY => 'Bad Gateway', + self::STATUS_SERVICE_UNAVAILABLE => 'Service Unavailable', + self::STATUS_GATEWAY_TIMEOUT => 'Gateway Timeout', + self::STATUS_HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported', + self::STATUS_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + self::STATUS_INSUFFICIENT_STORAGE => 'Insufficient Storage', // RFC 4918 + self::STATUS_LOOP_DETECTED => 'Loop Detected', // RFC 5842 + self::STATUS_BANDWIDTH_LIMIT_EXCEEDED => 'Bandwidth Limit Exceeded', // non-standard + self::STATUS_NOT_EXTENDED => 'Not extended', + self::STATUS_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', // draft-nottingham-http-new-status + ]; + } + + + /** + * Gets the correct header + * @param int Http::CONSTANT $status the constant from the Http class + * @param \DateTime $lastModified formatted last modified date + * @param string $ETag the etag + * @return string + */ + public function getStatusHeader($status) { + // we have one change currently for the http 1.0 header that differs + // from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND + // if this differs any more, we want to create childclasses for this + if ($status === self::STATUS_TEMPORARY_REDIRECT + && $this->protocolVersion === 'HTTP/1.0') { + $status = self::STATUS_FOUND; + } + + return $this->protocolVersion . ' ' . $status . ' ' . + $this->headers[$status]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Dispatcher.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Dispatcher.php new file mode 100644 index 0000000..3892bb6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Dispatcher.php @@ -0,0 +1,192 @@ + + * @author Christoph Wurst + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Http; + +use OC\AppFramework\Http; +use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Utility\ControllerMethodReflector; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\Response; +use OCP\IRequest; + +/** + * Class to dispatch the request to the middleware dispatcher + */ +class Dispatcher { + + /** @var MiddlewareDispatcher */ + private $middlewareDispatcher; + + /** @var Http */ + private $protocol; + + /** @var ControllerMethodReflector */ + private $reflector; + + /** @var IRequest */ + private $request; + + /** + * @param Http $protocol the http protocol with contains all status headers + * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which + * runs the middleware + * @param ControllerMethodReflector $reflector the reflector that is used to inject + * the arguments for the controller + * @param IRequest $request the incoming request + */ + public function __construct(Http $protocol, + MiddlewareDispatcher $middlewareDispatcher, + ControllerMethodReflector $reflector, + IRequest $request) { + $this->protocol = $protocol; + $this->middlewareDispatcher = $middlewareDispatcher; + $this->reflector = $reflector; + $this->request = $request; + } + + + /** + * Handles a request and calls the dispatcher on the controller + * @param Controller $controller the controller which will be called + * @param string $methodName the method name which will be called on + * the controller + * @return array $array[0] contains a string with the http main header, + * $array[1] contains headers in the form: $key => value, $array[2] contains + * the response output + * @throws \Exception + */ + public function dispatch(Controller $controller, string $methodName): array { + $out = [null, [], null]; + + try { + // prefill reflector with everything thats needed for the + // middlewares + $this->reflector->reflect($controller, $methodName); + + $this->middlewareDispatcher->beforeController($controller, + $methodName); + $response = $this->executeController($controller, $methodName); + + // if an exception appears, the middleware checks if it can handle the + // exception and creates a response. If no response is created, it is + // assumed that theres no middleware who can handle it and the error is + // thrown again + } catch (\Exception $exception) { + $response = $this->middlewareDispatcher->afterException( + $controller, $methodName, $exception); + } catch (\Throwable $throwable) { + $exception = new \Exception($throwable->getMessage(), $throwable->getCode(), $throwable); + $response = $this->middlewareDispatcher->afterException( + $controller, $methodName, $exception); + } + + $response = $this->middlewareDispatcher->afterController( + $controller, $methodName, $response); + + // depending on the cache object the headers need to be changed + $out[0] = $this->protocol->getStatusHeader($response->getStatus()); + $out[1] = array_merge($response->getHeaders()); + $out[2] = $response->getCookies(); + $out[3] = $this->middlewareDispatcher->beforeOutput( + $controller, $methodName, $response->render() + ); + $out[4] = $response; + + return $out; + } + + + /** + * Uses the reflected parameters, types and request parameters to execute + * the controller + * @param Controller $controller the controller to be executed + * @param string $methodName the method on the controller that should be executed + * @return Response + */ + private function executeController(Controller $controller, string $methodName): Response { + $arguments = []; + + // valid types that will be casted + $types = ['int', 'integer', 'bool', 'boolean', 'float']; + + foreach ($this->reflector->getParameters() as $param => $default) { + + // try to get the parameter from the request object and cast + // it to the type annotated in the @param annotation + $value = $this->request->getParam($param, $default); + $type = $this->reflector->getType($param); + + // if this is submitted using GET or a POST form, 'false' should be + // converted to false + if (($type === 'bool' || $type === 'boolean') && + $value === 'false' && + ( + $this->request->method === 'GET' || + strpos($this->request->getHeader('Content-Type'), + 'application/x-www-form-urlencoded') !== false + ) + ) { + $value = false; + } elseif ($value !== null && \in_array($type, $types, true)) { + settype($value, $type); + } + + $arguments[] = $value; + } + + $response = \call_user_func_array([$controller, $methodName], $arguments); + + // format response + if ($response instanceof DataResponse || !($response instanceof Response)) { + + // get format from the url format or request format parameter + $format = $this->request->getParam('format'); + + // if none is given try the first Accept header + if ($format === null) { + $headers = $this->request->getHeader('Accept'); + $format = $controller->getResponderByHTTPHeader($headers, null); + } + + if ($format !== null) { + $response = $controller->buildResponse($response, $format); + } else { + $response = $controller->buildResponse($response); + } + } + + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Output.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Output.php new file mode 100644 index 0000000..45d8d9b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Output.php @@ -0,0 +1,112 @@ + + * @author Lukas Reschke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Http; + +use OCP\AppFramework\Http\IOutput; + +/** + * Very thin wrapper class to make output testable + */ +class Output implements IOutput { + /** @var string */ + private $webRoot; + + /** + * @param $webRoot + */ + public function __construct($webRoot) { + $this->webRoot = $webRoot; + } + + /** + * @param string $out + */ + public function setOutput($out) { + print($out); + } + + /** + * @param string|resource $path or file handle + * + * @return bool false if an error occurred + */ + public function setReadfile($path) { + if (is_resource($path)) { + $output = fopen('php://output', 'w'); + return stream_copy_to_stream($path, $output) > 0; + } else { + return @readfile($path); + } + } + + /** + * @param string $header + */ + public function setHeader($header) { + header($header); + } + + /** + * @param int $code sets the http status code + */ + public function setHttpResponseCode($code) { + http_response_code($code); + } + + /** + * @return int returns the current http response code + */ + public function getHttpResponseCode() { + return http_response_code(); + } + + /** + * @param string $name + * @param string $value + * @param int $expire + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + */ + public function setCookie($name, $value, $expire, $path, $domain, $secure, $httpOnly, $sameSite = 'Lax') { + $path = $this->webRoot ? : '/'; + + if (PHP_VERSION_ID < 70300) { + setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); + } else { + setcookie($name, $value, [ + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httpOnly, + 'samesite' => $sameSite + ]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Request.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Request.php new file mode 100644 index 0000000..dcc3c8e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Http/Request.php @@ -0,0 +1,937 @@ + + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Georg Ehrke + * @author Joas Schilling + * @author Juan Pablo Villafáñez + * @author Julius Härtl + * @author Lukas Reschke + * @author Mitar + * @author Morris Jobke + * @author Oliver Wegner + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Http; + +use OC\Security\CSRF\CsrfToken; +use OC\Security\CSRF\CsrfTokenManager; +use OC\Security\TrustedDomainHelper; +use OCP\IConfig; +use OCP\IRequest; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; + +/** + * Class for accessing variables in the request. + * This class provides an immutable object with request variables. + * + * @property mixed[] cookies + * @property mixed[] env + * @property mixed[] files + * @property string method + * @property mixed[] parameters + * @property mixed[] server + */ +class Request implements \ArrayAccess, \Countable, IRequest { + public const USER_AGENT_IE = '/(MSIE)|(Trident)/'; + // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx + public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/'; + // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference + public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/'; + // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent + public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/'; + // Safari User Agent from http://www.useragentstring.com/pages/Safari/ + public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/'; + // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent + public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#'; + public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#'; + public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|::1)$/'; + + /** + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead + */ + public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/'; + /** + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead + */ + public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/'; + /** + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead + */ + public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/'; + + protected $inputStream; + protected $content; + protected $items = []; + protected $allowedKeys = [ + 'get', + 'post', + 'files', + 'server', + 'env', + 'cookies', + 'urlParams', + 'parameters', + 'method', + 'requesttoken', + ]; + /** @var ISecureRandom */ + protected $secureRandom; + /** @var IConfig */ + protected $config; + /** @var string */ + protected $requestId = ''; + /** @var ICrypto */ + protected $crypto; + /** @var CsrfTokenManager|null */ + protected $csrfTokenManager; + + /** @var bool */ + protected $contentDecoded = false; + + /** + * @param array $vars An associative array with the following optional values: + * - array 'urlParams' the parameters which were matched from the URL + * - array 'get' the $_GET array + * - array|string 'post' the $_POST array or JSON string + * - array 'files' the $_FILES array + * - array 'server' the $_SERVER array + * - array 'env' the $_ENV array + * - array 'cookies' the $_COOKIE array + * - string 'method' the request method (GET, POST etc) + * - string|false 'requesttoken' the requesttoken or false when not available + * @param ISecureRandom $secureRandom + * @param IConfig $config + * @param CsrfTokenManager|null $csrfTokenManager + * @param string $stream + * @see http://www.php.net/manual/en/reserved.variables.php + */ + public function __construct(array $vars= [], + ISecureRandom $secureRandom = null, + IConfig $config, + CsrfTokenManager $csrfTokenManager = null, + string $stream = 'php://input') { + $this->inputStream = $stream; + $this->items['params'] = []; + $this->secureRandom = $secureRandom; + $this->config = $config; + $this->csrfTokenManager = $csrfTokenManager; + + if (!array_key_exists('method', $vars)) { + $vars['method'] = 'GET'; + } + + foreach ($this->allowedKeys as $name) { + $this->items[$name] = isset($vars[$name]) + ? $vars[$name] + : []; + } + + $this->items['parameters'] = array_merge( + $this->items['get'], + $this->items['post'], + $this->items['urlParams'], + $this->items['params'] + ); + } + /** + * @param array $parameters + */ + public function setUrlParameters(array $parameters) { + $this->items['urlParams'] = $parameters; + $this->items['parameters'] = array_merge( + $this->items['parameters'], + $this->items['urlParams'] + ); + } + + /** + * Countable method + * @return int + */ + public function count(): int { + return \count($this->items['parameters']); + } + + /** + * ArrayAccess methods + * + * Gives access to the combined GET, POST and urlParams arrays + * + * Examples: + * + * $var = $request['myvar']; + * + * or + * + * if(!isset($request['myvar']) { + * // Do something + * } + * + * $request['myvar'] = 'something'; // This throws an exception. + * + * @param string $offset The key to lookup + * @return boolean + */ + public function offsetExists($offset): bool { + return isset($this->items['parameters'][$offset]); + } + + /** + * @see offsetExists + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) { + return isset($this->items['parameters'][$offset]) + ? $this->items['parameters'][$offset] + : null; + } + + /** + * @see offsetExists + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * @see offsetExists + * @param string $offset + */ + public function offsetUnset($offset) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * Magic property accessors + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * Access request variables by method and name. + * Examples: + * + * $request->post['myvar']; // Only look for POST variables + * $request->myvar; or $request->{'myvar'}; or $request->{$myvar} + * Looks in the combined GET, POST and urlParams array. + * + * If you access e.g. ->post but the current HTTP request method + * is GET a \LogicException will be thrown. + * + * @param string $name The key to look for. + * @throws \LogicException + * @return mixed|null + */ + public function __get($name) { + switch ($name) { + case 'put': + case 'patch': + case 'get': + case 'post': + if ($this->method !== strtoupper($name)) { + throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method)); + } + return $this->getContent(); + case 'files': + case 'server': + case 'env': + case 'cookies': + case 'urlParams': + case 'method': + return isset($this->items[$name]) + ? $this->items[$name] + : null; + case 'parameters': + case 'params': + return $this->getContent(); + default: + return isset($this[$name]) + ? $this[$name] + : null; + } + } + + /** + * @param string $name + * @return bool + */ + public function __isset($name) { + if (\in_array($name, $this->allowedKeys, true)) { + return true; + } + return isset($this->items['parameters'][$name]); + } + + /** + * @param string $id + */ + public function __unset($id) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * Returns the value for a specific http header. + * + * This method returns an empty string if the header did not exist. + * + * @param string $name + * @return string + */ + public function getHeader(string $name): string { + $name = strtoupper(str_replace('-', '_',$name)); + if (isset($this->server['HTTP_' . $name])) { + return $this->server['HTTP_' . $name]; + } + + // There's a few headers that seem to end up in the top-level + // server array. + switch ($name) { + case 'CONTENT_TYPE': + case 'CONTENT_LENGTH': + case 'REMOTE_ADDR': + if (isset($this->server[$name])) { + return $this->server[$name]; + } + break; + } + + return ''; + } + + /** + * Lets you access post and get parameters by the index + * In case of json requests the encoded json body is accessed + * + * @param string $key the key which you want to access in the URL Parameter + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters + * @param mixed $default If the key is not found, this value will be returned + * @return mixed the content of the array + */ + public function getParam(string $key, $default = null) { + return isset($this->parameters[$key]) + ? $this->parameters[$key] + : $default; + } + + /** + * Returns all params that were received, be it from the request + * (as GET or POST) or throuh the URL by the route + * @return array the array with all parameters + */ + public function getParams(): array { + return is_array($this->parameters) ? $this->parameters : []; + } + + /** + * Returns the method of the request + * @return string the method of the request (POST, GET, etc) + */ + public function getMethod(): string { + return $this->method; + } + + /** + * Shortcut for accessing an uploaded file through the $_FILES array + * @param string $key the key that will be taken from the $_FILES array + * @return array the file in the $_FILES element + */ + public function getUploadedFile(string $key) { + return isset($this->files[$key]) ? $this->files[$key] : null; + } + + /** + * Shortcut for getting env variables + * @param string $key the key that will be taken from the $_ENV array + * @return array the value in the $_ENV element + */ + public function getEnv(string $key) { + return isset($this->env[$key]) ? $this->env[$key] : null; + } + + /** + * Shortcut for getting cookie variables + * @param string $key the key that will be taken from the $_COOKIE array + * @return string the value in the $_COOKIE element + */ + public function getCookie(string $key) { + return isset($this->cookies[$key]) ? $this->cookies[$key] : null; + } + + /** + * Returns the request body content. + * + * If the HTTP request method is PUT and the body + * not application/x-www-form-urlencoded or application/json a stream + * resource is returned, otherwise an array. + * + * @return array|string|resource The request body content or a resource to read the body stream. + * + * @throws \LogicException + */ + protected function getContent() { + // If the content can't be parsed into an array then return a stream resource. + if ($this->method === 'PUT' + && $this->getHeader('Content-Length') !== '0' + && $this->getHeader('Content-Length') !== '' + && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false + && strpos($this->getHeader('Content-Type'), 'application/json') === false + ) { + if ($this->content === false) { + throw new \LogicException( + '"put" can only be accessed once if not ' + . 'application/x-www-form-urlencoded or application/json.' + ); + } + $this->content = false; + return fopen($this->inputStream, 'rb'); + } else { + $this->decodeContent(); + return $this->items['parameters']; + } + } + + /** + * Attempt to decode the content and populate parameters + */ + protected function decodeContent() { + if ($this->contentDecoded) { + return; + } + $params = []; + + // 'application/json' must be decoded manually. + if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) { + $params = json_decode(file_get_contents($this->inputStream), true); + if ($params !== null && \count($params) > 0) { + $this->items['params'] = $params; + if ($this->method === 'POST') { + $this->items['post'] = $params; + } + } + + // Handle application/x-www-form-urlencoded for methods other than GET + // or post correctly + } elseif ($this->method !== 'GET' + && $this->method !== 'POST' + && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) { + parse_str(file_get_contents($this->inputStream), $params); + if (\is_array($params)) { + $this->items['params'] = $params; + } + } + + if (\is_array($params)) { + $this->items['parameters'] = array_merge($this->items['parameters'], $params); + } + $this->contentDecoded = true; + } + + + /** + * Checks if the CSRF check was correct + * @return bool true if CSRF check passed + */ + public function passesCSRFCheck(): bool { + if ($this->csrfTokenManager === null) { + return false; + } + + if (!$this->passesStrictCookieCheck()) { + return false; + } + + if (isset($this->items['get']['requesttoken'])) { + $token = $this->items['get']['requesttoken']; + } elseif (isset($this->items['post']['requesttoken'])) { + $token = $this->items['post']['requesttoken']; + } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) { + $token = $this->items['server']['HTTP_REQUESTTOKEN']; + } else { + //no token found. + return false; + } + $token = new CsrfToken($token); + + return $this->csrfTokenManager->isTokenValid($token); + } + + /** + * Whether the cookie checks are required + * + * @return bool + */ + private function cookieCheckRequired(): bool { + if ($this->getHeader('OCS-APIREQUEST')) { + return false; + } + if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) { + return false; + } + + return true; + } + + /** + * Wrapper around session_get_cookie_params + * + * @return array + */ + public function getCookieParams(): array { + return session_get_cookie_params(); + } + + /** + * Appends the __Host- prefix to the cookie if applicable + * + * @param string $name + * @return string + */ + protected function getProtectedCookieName(string $name): string { + $cookieParams = $this->getCookieParams(); + $prefix = ''; + if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') { + $prefix = '__Host-'; + } + + return $prefix.$name; + } + + /** + * Checks if the strict cookie has been sent with the request if the request + * is including any cookies. + * + * @return bool + * @since 9.1.0 + */ + public function passesStrictCookieCheck(): bool { + if (!$this->cookieCheckRequired()) { + return true; + } + + $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict'); + if ($this->getCookie($cookieName) === 'true' + && $this->passesLaxCookieCheck()) { + return true; + } + return false; + } + + /** + * Checks if the lax cookie has been sent with the request if the request + * is including any cookies. + * + * @return bool + * @since 9.1.0 + */ + public function passesLaxCookieCheck(): bool { + if (!$this->cookieCheckRequired()) { + return true; + } + + $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax'); + if ($this->getCookie($cookieName) === 'true') { + return true; + } + return false; + } + + + /** + * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging + * If `mod_unique_id` is installed this value will be taken. + * @return string + */ + public function getId(): string { + if (isset($this->server['UNIQUE_ID'])) { + return $this->server['UNIQUE_ID']; + } + + if (empty($this->requestId)) { + $validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS; + $this->requestId = $this->secureRandom->generate(20, $validChars); + } + + return $this->requestId; + } + + /** + * Checks if given $remoteAddress matches given $trustedProxy. + * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if + * $remoteAddress is an IPv4 address within that IP range. + * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result + * will be returned. + * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise + */ + protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { + $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; + + if (preg_match($cidrre, $trustedProxy, $match)) { + $net = $match[1]; + $shiftbits = min(32, max(0, 32 - intval($match[2]))); + $netnum = ip2long($net) >> $shiftbits; + $ipnum = ip2long($remoteAddress) >> $shiftbits; + + return $ipnum === $netnum; + } + + return $trustedProxy === $remoteAddress; + } + + /** + * Checks if given $remoteAddress matches any entry in the given array $trustedProxies. + * For details regarding what "match" means, refer to `matchesTrustedProxy`. + * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise + */ + protected function isTrustedProxy($trustedProxies, $remoteAddress) { + foreach ($trustedProxies as $tp) { + if ($this->matchesTrustedProxy($tp, $remoteAddress)) { + return true; + } + } + + return false; + } + + /** + * Returns the remote address, if the connection came from a trusted proxy + * and `forwarded_for_headers` has been configured then the IP address + * specified in this header will be returned instead. + * Do always use this instead of $_SERVER['REMOTE_ADDR'] + * @return string IP address + */ + public function getRemoteAddress(): string { + $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; + $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); + + if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) { + $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [ + 'HTTP_X_FORWARDED_FOR' + // only have one default, so we cannot ship an insecure product out of the box + ]); + + foreach ($forwardedForHeaders as $header) { + if (isset($this->server[$header])) { + foreach (explode(',', $this->server[$header]) as $IP) { + $IP = trim($IP); + + // remove brackets from IPv6 addresses + if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') { + $IP = substr($IP, 1, -1); + } + + if (filter_var($IP, FILTER_VALIDATE_IP) !== false) { + return $IP; + } + } + } + } + } + + return $remoteAddress; + } + + /** + * Check overwrite condition + * @param string $type + * @return bool + */ + private function isOverwriteCondition(string $type = ''): bool { + $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/'; + $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; + return $regex === '//' || preg_match($regex, $remoteAddr) === 1 + || $type !== 'protocol'; + } + + /** + * Returns the server protocol. It respects one or more reverse proxies servers + * and load balancers + * @return string Server protocol (http or https) + */ + public function getServerProtocol(): string { + if ($this->config->getSystemValue('overwriteprotocol') !== '' + && $this->isOverwriteCondition('protocol')) { + return $this->config->getSystemValue('overwriteprotocol'); + } + + if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) { + if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) { + $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']); + $proto = strtolower(trim($parts[0])); + } else { + $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']); + } + + // Verify that the protocol is always HTTP or HTTPS + // default to http if an invalid value is provided + return $proto === 'https' ? 'https' : 'http'; + } + + if (isset($this->server['HTTPS']) + && $this->server['HTTPS'] !== null + && $this->server['HTTPS'] !== 'off' + && $this->server['HTTPS'] !== '') { + return 'https'; + } + + return 'http'; + } + + /** + * Returns the used HTTP protocol. + * + * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. + */ + public function getHttpProtocol(): string { + $claimedProtocol = $this->server['SERVER_PROTOCOL']; + + if (\is_string($claimedProtocol)) { + $claimedProtocol = strtoupper($claimedProtocol); + } + + $validProtocols = [ + 'HTTP/1.0', + 'HTTP/1.1', + 'HTTP/2', + ]; + + if (\in_array($claimedProtocol, $validProtocols, true)) { + return $claimedProtocol; + } + + return 'HTTP/1.1'; + } + + /** + * Returns the request uri, even if the website uses one or more + * reverse proxies + * @return string + */ + public function getRequestUri(): string { + $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; + if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) { + $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME'])); + } + return $uri; + } + + /** + * Get raw PathInfo from request (not urldecoded) + * @throws \Exception + * @return string Path info + */ + public function getRawPathInfo(): string { + $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; + // remove too many slashes - can be caused by reverse proxy configuration + $requestUri = preg_replace('%/{2,}%', '/', $requestUri); + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + $scriptName = $this->server['SCRIPT_NAME']; + $pathInfo = $requestUri; + + // strip off the script name's dir and file name + // FIXME: Sabre does not really belong here + list($path, $name) = \Sabre\Uri\split($scriptName); + if (!empty($path)) { + if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) { + $pathInfo = substr($pathInfo, \strlen($path)); + } else { + throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')"); + } + } + if ($name === null) { + $name = ''; + } + + if (strpos($pathInfo, '/'.$name) === 0) { + $pathInfo = substr($pathInfo, \strlen($name) + 1); + } + if ($name !== '' && strpos($pathInfo, $name) === 0) { + $pathInfo = substr($pathInfo, \strlen($name)); + } + if ($pathInfo === false || $pathInfo === '/') { + return ''; + } else { + return $pathInfo; + } + } + + /** + * Get PathInfo from request + * @throws \Exception + * @return string|false Path info or false when not found + */ + public function getPathInfo() { + $pathInfo = $this->getRawPathInfo(); + // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment + $pathInfo = rawurldecode($pathInfo); + $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']); + + switch ($encoding) { + case 'ISO-8859-1': + $pathInfo = utf8_encode($pathInfo); + } + // end copy + + return $pathInfo; + } + + /** + * Returns the script name, even if the website uses one or more + * reverse proxies + * @return string the script name + */ + public function getScriptName(): string { + $name = $this->server['SCRIPT_NAME']; + $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); + if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { + // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous + $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); + $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot))); + $name = '/' . ltrim($overwriteWebRoot . $suburi, '/'); + } + return $name; + } + + /** + * Checks whether the user agent matches a given regex + * @param array $agent array of agent names + * @return bool true if at least one of the given agent matches, false otherwise + */ + public function isUserAgent(array $agent): bool { + if (!isset($this->server['HTTP_USER_AGENT'])) { + return false; + } + foreach ($agent as $regex) { + if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) { + return true; + } + } + return false; + } + + /** + * Returns the unverified server host from the headers without checking + * whether it is a trusted domain + * @return string Server host + */ + public function getInsecureServerHost(): string { + if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) { + return $this->getOverwriteHost(); + } + + $host = 'localhost'; + if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) { + if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) { + $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']); + $host = trim(current($parts)); + } else { + $host = $this->server['HTTP_X_FORWARDED_HOST']; + } + } else { + if (isset($this->server['HTTP_HOST'])) { + $host = $this->server['HTTP_HOST']; + } elseif (isset($this->server['SERVER_NAME'])) { + $host = $this->server['SERVER_NAME']; + } + } + + return $host; + } + + + /** + * Returns the server host from the headers, or the first configured + * trusted domain if the host isn't in the trusted list + * @return string Server host + */ + public function getServerHost(): string { + // overwritehost is always trusted + $host = $this->getOverwriteHost(); + if ($host !== null) { + return $host; + } + + // get the host from the headers + $host = $this->getInsecureServerHost(); + + // Verify that the host is a trusted domain if the trusted domains + // are defined + // If no trusted domain is provided the first trusted domain is returned + $trustedDomainHelper = new TrustedDomainHelper($this->config); + if ($trustedDomainHelper->isTrustedDomain($host)) { + return $host; + } + + $trustedList = (array)$this->config->getSystemValue('trusted_domains', []); + if (count($trustedList) > 0) { + return reset($trustedList); + } + + return ''; + } + + /** + * Returns the overwritehost setting from the config if set and + * if the overwrite condition is met + * @return string|null overwritehost value or null if not defined or the defined condition + * isn't met + */ + private function getOverwriteHost() { + if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) { + return $this->config->getSystemValue('overwritehost'); + } + return null; + } + + private function fromTrustedProxy(): bool { + $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; + $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); + + return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Logger.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Logger.php new file mode 100644 index 0000000..16f4dd3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Logger.php @@ -0,0 +1,128 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework; + +use OCP\ILogger; + +/** + * @deprecated + */ +class Logger implements ILogger { + + /** @var ILogger */ + private $logger; + + /** @var string */ + private $appName; + + /** + * @deprecated + */ + public function __construct(ILogger $logger, string $appName) { + $this->logger = $logger; + $this->appName = $appName; + } + + private function extendContext(array $context): array { + if (!isset($context['app'])) { + $context['app'] = $this->appName; + } + + return $context; + } + + /** + * @deprecated + */ + public function emergency(string $message, array $context = []) { + $this->logger->emergency($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function alert(string $message, array $context = []) { + $this->logger->alert($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function critical(string $message, array $context = []) { + $this->logger->critical($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function error(string $message, array $context = []) { + $this->logger->emergency($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function warning(string $message, array $context = []) { + $this->logger->warning($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function notice(string $message, array $context = []) { + $this->logger->notice($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function info(string $message, array $context = []) { + $this->logger->info($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function debug(string $message, array $context = []) { + $this->logger->debug($message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function log(int $level, string $message, array $context = []) { + $this->logger->log($level, $message, $this->extendContext($context)); + } + + /** + * @deprecated + */ + public function logException(\Throwable $exception, array $context = []) { + $this->logger->logException($exception, $this->extendContext($context)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php new file mode 100644 index 0000000..28f322f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php @@ -0,0 +1,79 @@ + + * + * @author Julius Härtl + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware; + +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\PublicShareController; +use OCP\EventDispatcher\GenericEvent; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IUserSession; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class AdditionalScriptsMiddleware extends Middleware { + /** @var EventDispatcherInterface */ + private $legacyDispatcher; + /** @var IUserSession */ + private $userSession; + /** @var IEventDispatcher */ + private $dispatcher; + + public function __construct(EventDispatcherInterface $legacyDispatcher, IUserSession $userSession, IEventDispatcher $dispatcher) { + $this->legacyDispatcher = $legacyDispatcher; + $this->userSession = $userSession; + $this->dispatcher = $dispatcher; + } + + public function afterController($controller, $methodName, Response $response): Response { + if ($response instanceof TemplateResponse) { + if (!$controller instanceof PublicShareController) { + /* + * The old event was not dispatched on the public share controller as there was + * OCA\Files_Sharing::loadAdditionalScripts for that. This is kept for compatibility reasons + * only for the old event as this is now also included in BeforeTemplateRenderedEvent + */ + $this->legacyDispatcher->dispatch(TemplateResponse::EVENT_LOAD_ADDITIONAL_SCRIPTS, new GenericEvent()); + } + + if (!($response instanceof StandaloneTemplateResponse) && $this->userSession->isLoggedIn()) { + $this->legacyDispatcher->dispatch(TemplateResponse::EVENT_LOAD_ADDITIONAL_SCRIPTS_LOGGEDIN, new GenericEvent()); + $isLoggedIn = true; + } else { + $isLoggedIn = false; + } + + $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn, $response)); + } + + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/CompressionMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/CompressionMiddleware.php new file mode 100644 index 0000000..f6d3839 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/CompressionMiddleware.php @@ -0,0 +1,92 @@ + + * + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\OCS\BaseResponse; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Middleware; +use OCP\IRequest; + +class CompressionMiddleware extends Middleware { + + /** @var bool */ + private $useGZip; + + /** @var IRequest */ + private $request; + + public function __construct(IRequest $request) { + $this->request = $request; + $this->useGZip = false; + } + + public function afterController($controller, $methodName, Response $response) { + // By default we do not gzip + $allowGzip = false; + + // Only return gzipped content for 200 responses + if ($response->getStatus() !== Http::STATUS_OK) { + return $response; + } + + // Check if we are even asked for gzip + $header = $this->request->getHeader('Accept-Encoding'); + if (strpos($header, 'gzip') === false) { + return $response; + } + + // We only allow gzip in some cases + if ($response instanceof BaseResponse) { + $allowGzip = true; + } + if ($response instanceof JSONResponse) { + $allowGzip = true; + } + if ($response instanceof TemplateResponse) { + $allowGzip = true; + } + + if ($allowGzip) { + $this->useGZip = true; + $response->addHeader('Content-Encoding', 'gzip'); + } + + return $response; + } + + public function beforeOutput($controller, $methodName, $output) { + if (!$this->useGZip) { + return $output; + } + + return gzencode($output); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php new file mode 100644 index 0000000..270058d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php @@ -0,0 +1,167 @@ + + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; + +/** + * This class is used to store and run all the middleware in correct order + */ +class MiddlewareDispatcher { + + /** + * @var array array containing all the middlewares + */ + private $middlewares; + + /** + * @var int counter which tells us what middlware was executed once an + * exception occurs + */ + private $middlewareCounter; + + + /** + * Constructor + */ + public function __construct() { + $this->middlewares = []; + $this->middlewareCounter = 0; + } + + + /** + * Adds a new middleware + * @param Middleware $middleWare the middleware which will be added + */ + public function registerMiddleware(Middleware $middleWare) { + $this->middlewares[] = $middleWare; + } + + + /** + * returns an array with all middleware elements + * @return array the middlewares + */ + public function getMiddlewares(): array { + return $this->middlewares; + } + + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + */ + public function beforeController(Controller $controller, string $methodName) { + // we need to count so that we know which middlewares we have to ask in + // case there is an exception + $middlewareCount = \count($this->middlewares); + for ($i = 0; $i < $middlewareCount; $i++) { + $this->middlewareCounter++; + $middleware = $this->middlewares[$i]; + $middleware->beforeController($controller, $methodName); + } + } + + + /** + * This is being run when either the beforeController method or the + * controller method itself is throwing an exception. The middleware is asked + * in reverse order to handle the exception and to return a response. + * If the response is null, it is assumed that the exception could not be + * handled and the error will be thrown again + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @return Response a Response object if the middleware can handle the + * exception + * @throws \Exception the passed in exception if it can't handle it + */ + public function afterException(Controller $controller, string $methodName, \Exception $exception): Response { + for ($i=$this->middlewareCounter-1; $i>=0; $i--) { + $middleware = $this->middlewares[$i]; + try { + return $middleware->afterException($controller, $methodName, $exception); + } catch (\Exception $exception) { + continue; + } + } + throw $exception; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController(Controller $controller, string $methodName, Response $response): Response { + for ($i= \count($this->middlewares)-1; $i>=0; $i--) { + $middleware = $this->middlewares[$i]; + $response = $middleware->afterController($controller, $methodName, $response); + } + return $response; + } + + + /** + * This is being run after the response object has been rendered and + * allows the manipulation of the output. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param string $output the generated output from a response + * @return string the output that should be printed + */ + public function beforeOutput(Controller $controller, string $methodName, string $output): string { + for ($i= \count($this->middlewares)-1; $i>=0; $i--) { + $middleware = $this->middlewares[$i]; + $output = $middleware->beforeOutput($controller, $methodName, $output); + } + return $output; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php new file mode 100644 index 0000000..083761d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php @@ -0,0 +1,57 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; +use OCP\IRequest; + +class NotModifiedMiddleware extends Middleware { + /** @var IRequest */ + private $request; + + public function __construct(IRequest $request) { + $this->request = $request; + } + + public function afterController($controller, $methodName, Response $response) { + $etagHeader = $this->request->getHeader('IF_NONE_MATCH'); + if ($etagHeader !== '' && $response->getETag() !== null && trim($etagHeader) === '"' . $response->getETag() . '"') { + $response->setStatus(Http::STATUS_NOT_MODIFIED); + return $response; + } + + $modifiedSinceHeader = $this->request->getHeader('IF_MODIFIED_SINCE'); + if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTime::RFC2822)) { + $response->setStatus(Http::STATUS_NOT_MODIFIED); + return $response; + } + + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/OCSMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/OCSMiddleware.php new file mode 100644 index 0000000..5016fc3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/OCSMiddleware.php @@ -0,0 +1,155 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Http; +use OC\AppFramework\OCS\BaseResponse; +use OC\AppFramework\OCS\V1Response; +use OC\AppFramework\OCS\V2Response; +use OCP\API; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; + +class OCSMiddleware extends Middleware { + + /** @var IRequest */ + private $request; + + /** @var int */ + private $ocsVersion; + + /** + * @param IRequest $request + */ + public function __construct(IRequest $request) { + $this->request = $request; + } + + /** + * @param Controller $controller + * @param string $methodName + */ + public function beforeController($controller, $methodName) { + if ($controller instanceof OCSController) { + if (substr_compare($this->request->getScriptName(), '/ocs/v2.php', -strlen('/ocs/v2.php')) === 0) { + $this->ocsVersion = 2; + } else { + $this->ocsVersion = 1; + } + $controller->setOCSVersion($this->ocsVersion); + } + } + + /** + * @param Controller $controller + * @param string $methodName + * @param \Exception $exception + * @throws \Exception + * @return BaseResponse + */ + public function afterException($controller, $methodName, \Exception $exception) { + if ($controller instanceof OCSController && $exception instanceof OCSException) { + $code = $exception->getCode(); + if ($code === 0) { + $code = API::RESPOND_UNKNOWN_ERROR; + } + + return $this->buildNewResponse($controller, $code, $exception->getMessage()); + } + + throw $exception; + } + + /** + * @param Controller $controller + * @param string $methodName + * @param Response $response + * @return \OCP\AppFramework\Http\Response + */ + public function afterController($controller, $methodName, Response $response) { + /* + * If a different middleware has detected that a request unauthorized or forbidden + * we need to catch the response and convert it to a proper OCS response. + */ + if ($controller instanceof OCSController && !($response instanceof BaseResponse)) { + if ($response->getStatus() === Http::STATUS_UNAUTHORIZED || + $response->getStatus() === Http::STATUS_FORBIDDEN) { + $message = ''; + if ($response instanceof JSONResponse) { + /** @var DataResponse $response */ + $message = $response->getData()['message']; + } + + return $this->buildNewResponse($controller, API::RESPOND_UNAUTHORISED, $message); + } + } + + return $response; + } + + /** + * @param Controller $controller + * @param int $code + * @param string $message + * @return V1Response|V2Response + */ + private function buildNewResponse(Controller $controller, $code, $message) { + $format = $this->getFormat($controller); + + $data = new DataResponse(); + $data->setStatus($code); + if ($this->ocsVersion === 1) { + $response = new V1Response($data, $format, $message); + } else { + $response = new V2Response($data, $format, $message); + } + + return $response; + } + + /** + * @param Controller $controller + * @return string + */ + private function getFormat(Controller $controller) { + // get format from the url format or request format parameter + $format = $this->request->getParam('format'); + + // if none is given try the first Accept header + if ($format === null) { + $headers = $this->request->getHeader('Accept'); + $format = $controller->getResponderByHTTPHeader($headers, 'xml'); + } + + return $format; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php new file mode 100644 index 0000000..b362a38 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php @@ -0,0 +1,27 @@ + + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\AppFramework\Middleware\PublicShare\Exceptions; + +class NeedAuthenticationException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php new file mode 100644 index 0000000..4b2dd25 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php @@ -0,0 +1,131 @@ + + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\AppFramework\Middleware\PublicShare; + +use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; +use OCP\AppFramework\AuthPublicShareController; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\PublicShareController; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; + +class PublicShareMiddleware extends Middleware { + /** @var IRequest */ + private $request; + + /** @var ISession */ + private $session; + + /** @var IConfig */ + private $config; + + public function __construct(IRequest $request, ISession $session, IConfig $config) { + $this->request = $request; + $this->session = $session; + $this->config = $config; + } + + public function beforeController($controller, $methodName) { + if (!($controller instanceof PublicShareController)) { + return; + } + + if (!$this->isLinkSharingEnabled()) { + throw new NotFoundException('Link sharing is disabled'); + } + + // We require the token parameter to be set + $token = $this->request->getParam('token'); + if ($token === null) { + throw new NotFoundException(); + } + + // Set the token + $controller->setToken($token); + + if (!$controller->isValidToken()) { + $controller->shareNotFound(); + throw new NotFoundException(); + } + + // No need to check for authentication when we try to authenticate + if ($methodName === 'authenticate' || $methodName === 'showAuthenticate') { + return; + } + + // If authentication succeeds just continue + if ($controller->isAuthenticated()) { + return; + } + + // If we can authenticate to this controller do it else we throw a 404 to not leak any info + if ($controller instanceof AuthPublicShareController) { + $this->session->set('public_link_authenticate_redirect', json_encode($this->request->getParams())); + throw new NeedAuthenticationException(); + } + + throw new NotFoundException(); + } + + public function afterException($controller, $methodName, \Exception $exception) { + if (!($controller instanceof PublicShareController)) { + throw $exception; + } + + if ($exception instanceof NotFoundException) { + return new NotFoundResponse(); + } + + if ($controller instanceof AuthPublicShareController && $exception instanceof NeedAuthenticationException) { + return $controller->getAuthenticationRedirect($this->getFunctionForRoute($this->request->getParam('_route'))); + } + + throw $exception; + } + + private function getFunctionForRoute(string $route): string { + $tmp = explode('.', $route); + return array_pop($tmp); + } + + /** + * Check if link sharing is allowed + */ + private function isLinkSharingEnabled(): bool { + // Check if the shareAPI is enabled + if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') { + return false; + } + + // Check whether public sharing is enabled + if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') { + return false; + } + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php new file mode 100644 index 0000000..31a4791 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php @@ -0,0 +1,115 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\Bruteforce\Throttler; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TooManyRequestsResponse; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; +use OCP\Security\Bruteforce\MaxDelayReached; + +/** + * Class BruteForceMiddleware performs the bruteforce protection for controllers + * that are annotated with @BruteForceProtection(action=$action) whereas $action + * is the action that should be logged within the database. + * + * @package OC\AppFramework\Middleware\Security + */ +class BruteForceMiddleware extends Middleware { + /** @var ControllerMethodReflector */ + private $reflector; + /** @var Throttler */ + private $throttler; + /** @var IRequest */ + private $request; + + /** + * @param ControllerMethodReflector $controllerMethodReflector + * @param Throttler $throttler + * @param IRequest $request + */ + public function __construct(ControllerMethodReflector $controllerMethodReflector, + Throttler $throttler, + IRequest $request) { + $this->reflector = $controllerMethodReflector; + $this->throttler = $throttler; + $this->request = $request; + } + + /** + * {@inheritDoc} + */ + public function beforeController($controller, $methodName) { + parent::beforeController($controller, $methodName); + + if ($this->reflector->hasAnnotation('BruteForceProtection')) { + $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); + $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action); + } + } + + /** + * {@inheritDoc} + */ + public function afterController($controller, $methodName, Response $response) { + if ($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) { + $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); + $ip = $this->request->getRemoteAddress(); + $this->throttler->sleepDelay($ip, $action); + $this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata()); + } + + return parent::afterController($controller, $methodName, $response); + } + + /** + * @param Controller $controller + * @param string $methodName + * @param \Exception $exception + * @throws \Exception + * @return Response + */ + public function afterException($controller, $methodName, \Exception $exception): Response { + if ($exception instanceof MaxDelayReached) { + if ($controller instanceof OCSController) { + throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS); + } + + return new TooManyRequestsResponse(); + } + + throw $exception; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php new file mode 100644 index 0000000..af6d3de --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -0,0 +1,159 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; +use OC\Security\Bruteforce\Throttler; +use OC\User\Session; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; +use OCP\IRequest; + +/** + * This middleware sets the correct CORS headers on a response if the + * controller has the @CORS annotation. This is needed for webapps that want + * to access an API and don't run on the same domain, see + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + */ +class CORSMiddleware extends Middleware { + /** @var IRequest */ + private $request; + /** @var ControllerMethodReflector */ + private $reflector; + /** @var Session */ + private $session; + /** @var Throttler */ + private $throttler; + + /** + * @param IRequest $request + * @param ControllerMethodReflector $reflector + * @param Session $session + * @param Throttler $throttler + */ + public function __construct(IRequest $request, + ControllerMethodReflector $reflector, + Session $session, + Throttler $throttler) { + $this->request = $request; + $this->reflector = $reflector; + $this->session = $session; + $this->throttler = $throttler; + } + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @throws SecurityException + * @since 6.0.0 + */ + public function beforeController($controller, $methodName) { + // ensure that @CORS annotated API routes are not used in conjunction + // with session authentication since this enables CSRF attack vectors + if ($this->reflector->hasAnnotation('CORS') && + !$this->reflector->hasAnnotation('PublicPage')) { + $user = $this->request->server['PHP_AUTH_USER']; + $pass = $this->request->server['PHP_AUTH_PW']; + + $this->session->logout(); + try { + if (!$this->session->logClientIn($user, $pass, $this->request, $this->throttler)) { + throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED); + } + } catch (PasswordLoginForbiddenException $ex) { + throw new SecurityException('Password login forbidden, use token instead', Http::STATUS_UNAUTHORIZED); + } + } + } + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + * @throws SecurityException + */ + public function afterController($controller, $methodName, Response $response) { + // only react if its a CORS request and if the request sends origin and + + if (isset($this->request->server['HTTP_ORIGIN']) && + $this->reflector->hasAnnotation('CORS')) { + + // allow credentials headers must not be true or CSRF is possible + // otherwise + foreach ($response->getHeaders() as $header => $value) { + if (strtolower($header) === 'access-control-allow-credentials' && + strtolower(trim($value)) === 'true') { + $msg = 'Access-Control-Allow-Credentials must not be '. + 'set to true in order to prevent CSRF'; + throw new SecurityException($msg); + } + } + + $origin = $this->request->server['HTTP_ORIGIN']; + $response->addHeader('Access-Control-Allow-Origin', $origin); + } + return $response; + } + + /** + * If an SecurityException is being caught return a JSON error response + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it can't handle it + * @return Response a Response object or null in case that the exception could not be handled + */ + public function afterException($controller, $methodName, \Exception $exception) { + if ($exception instanceof SecurityException) { + $response = new JSONResponse(['message' => $exception->getMessage()]); + if ($exception->getCode() !== 0) { + $response->setStatus($exception->getCode()); + } else { + $response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR); + } + return $response; + } + + throw $exception; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php new file mode 100644 index 0000000..10768a6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php @@ -0,0 +1,83 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OC\Security\CSRF\CsrfTokenManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; + +class CSPMiddleware extends Middleware { + + /** @var ContentSecurityPolicyManager */ + private $contentSecurityPolicyManager; + /** @var ContentSecurityPolicyNonceManager */ + private $cspNonceManager; + /** @var CsrfTokenManager */ + private $csrfTokenManager; + + public function __construct(ContentSecurityPolicyManager $policyManager, + ContentSecurityPolicyNonceManager $cspNonceManager, + CsrfTokenManager $csrfTokenManager) { + $this->contentSecurityPolicyManager = $policyManager; + $this->cspNonceManager = $cspNonceManager; + $this->csrfTokenManager = $csrfTokenManager; + } + + /** + * Performs the default CSP modifications that may be injected by other + * applications + * + * @param Controller $controller + * @param string $methodName + * @param Response $response + * @return Response + */ + public function afterController($controller, $methodName, Response $response): Response { + $policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy(); + + if (get_class($policy) === EmptyContentSecurityPolicy::class) { + return $response; + } + + $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); + $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); + + if ($this->cspNonceManager->browserSupportsCspV3()) { + $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + + $response->setContentSecurityPolicy($defaultPolicy); + + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php new file mode 100644 index 0000000..d7bc7ed --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php @@ -0,0 +1,40 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class AppNotEnabledException is thrown when a resource for an application is + * requested that is not enabled. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class AppNotEnabledException extends SecurityException { + public function __construct() { + parent::__construct('App is not enabled', Http::STATUS_PRECONDITION_FAILED); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php new file mode 100644 index 0000000..abc7303 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php @@ -0,0 +1,40 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class CrossSiteRequestForgeryException is thrown when a CSRF exception has + * been encountered. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class CrossSiteRequestForgeryException extends SecurityException { + public function __construct() { + parent::__construct('CSRF check failed', Http::STATUS_PRECONDITION_FAILED); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php new file mode 100644 index 0000000..b5336d8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php @@ -0,0 +1,38 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class LaxSameSiteCookieFailedException is thrown when a request doesn't pass + * the required LaxCookie check on index.php + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class LaxSameSiteCookieFailedException extends SecurityException { + public function __construct() { + parent::__construct('Lax Same Site Cookie is invalid in request.', Http::STATUS_PRECONDITION_FAILED); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php new file mode 100644 index 0000000..7f5a03d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php @@ -0,0 +1,44 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class NotAdminException is thrown when a resource has been requested by a + * non-admin user that is not accessible to non-admin users. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class NotAdminException extends SecurityException { + public function __construct(string $message) { + parent::__construct($message, Http::STATUS_FORBIDDEN); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php new file mode 100644 index 0000000..5fd35c6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php @@ -0,0 +1,38 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class NotConfirmedException is thrown when a resource has been requested by a + * user that has not confirmed their password in the last 30 minutes. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class NotConfirmedException extends SecurityException { + public function __construct() { + parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php new file mode 100644 index 0000000..f792026 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php @@ -0,0 +1,40 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class NotLoggedInException is thrown when a resource has been requested by a + * guest user that is not accessible to the public. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class NotLoggedInException extends SecurityException { + public function __construct() { + parent::__construct('Current user is not logged in', Http::STATUS_UNAUTHORIZED); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php new file mode 100644 index 0000000..934cae9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php @@ -0,0 +1,30 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +class ReloadExecutionException extends SecurityException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php new file mode 100644 index 0000000..a28e22f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php @@ -0,0 +1,35 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +/** + * Class SecurityException is the base class for security exceptions thrown by + * the security middleware. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class SecurityException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php new file mode 100644 index 0000000..0d7d8c6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php @@ -0,0 +1,38 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security\Exceptions; + +use OCP\AppFramework\Http; + +/** + * Class StrictCookieMissingException is thrown when the strict cookie has not + * been sent with the request but is required. + * + * @package OC\AppFramework\Middleware\Security\Exceptions + */ +class StrictCookieMissingException extends SecurityException { + public function __construct() { + parent::__construct('Strict Cookie has not been found in request.', Http::STATUS_PRECONDITION_FAILED); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php new file mode 100644 index 0000000..63f665f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php @@ -0,0 +1,67 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\Security\FeaturePolicy\FeaturePolicy; +use OC\Security\FeaturePolicy\FeaturePolicyManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\EmptyFeaturePolicy; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; + +class FeaturePolicyMiddleware extends Middleware { + + /** @var FeaturePolicyManager */ + private $policyManager; + + public function __construct(FeaturePolicyManager $policyManager) { + $this->policyManager = $policyManager; + } + + /** + * Performs the default FeaturePolicy modifications that may be injected by other + * applications + * + * @param Controller $controller + * @param string $methodName + * @param Response $response + * @return Response + */ + public function afterController($controller, $methodName, Response $response): Response { + $policy = !is_null($response->getFeaturePolicy()) ? $response->getFeaturePolicy() : new FeaturePolicy(); + + if (get_class($policy) === EmptyFeaturePolicy::class) { + return $response; + } + + $defaultPolicy = $this->policyManager->getDefaultPolicy(); + $defaultPolicy = $this->policyManager->mergePolicies($defaultPolicy, $policy); + $response->setFeaturePolicy($defaultPolicy); + + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php new file mode 100644 index 0000000..b259490 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -0,0 +1,93 @@ + + * + * @author Bjoern Schiessle + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\ISession; +use OCP\IUserSession; +use OCP\User\Backend\IPasswordConfirmationBackend; + +class PasswordConfirmationMiddleware extends Middleware { + /** @var ControllerMethodReflector */ + private $reflector; + /** @var ISession */ + private $session; + /** @var IUserSession */ + private $userSession; + /** @var ITimeFactory */ + private $timeFactory; + /** @var array */ + private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; + + /** + * PasswordConfirmationMiddleware constructor. + * + * @param ControllerMethodReflector $reflector + * @param ISession $session + * @param IUserSession $userSession + * @param ITimeFactory $timeFactory + */ + public function __construct(ControllerMethodReflector $reflector, + ISession $session, + IUserSession $userSession, + ITimeFactory $timeFactory) { + $this->reflector = $reflector; + $this->session = $session; + $this->userSession = $userSession; + $this->timeFactory = $timeFactory; + } + + /** + * @param Controller $controller + * @param string $methodName + * @throws NotConfirmedException + */ + public function beforeController($controller, $methodName) { + if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) { + $user = $this->userSession->getUser(); + $backendClassName = ''; + if ($user !== null) { + $backend = $user->getBackend(); + if ($backend instanceof IPasswordConfirmationBackend) { + if (!$backend->canConfirmPassword($user->getUID())) { + return; + } + } + + $backendClassName = $user->getBackendClassName(); + } + + $lastConfirm = (int) $this->session->get('last-password-confirm'); + // we can't check the password against a SAML backend, so skip password confirmation in this case + if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay + throw new NotConfirmedException(); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php new file mode 100644 index 0000000..712becb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php @@ -0,0 +1,136 @@ + + * + * @author Christoph Wurst + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\RateLimiting\Exception\RateLimitExceededException; +use OC\Security\RateLimiting\Limiter; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Middleware; +use OCP\IRequest; +use OCP\IUserSession; + +/** + * Class RateLimitingMiddleware is the middleware responsible for implementing the + * ratelimiting in Nextcloud. + * + * It parses annotations such as: + * + * @UserRateThrottle(limit=5, period=100) + * @AnonRateThrottle(limit=1, period=100) + * + * Those annotations above would mean that logged-in users can access the page 5 + * times within 100 seconds, and anonymous users 1 time within 100 seconds. If + * only an AnonRateThrottle is specified that one will also be applied to logged-in + * users. + * + * @package OC\AppFramework\Middleware\Security + */ +class RateLimitingMiddleware extends Middleware { + /** @var IRequest $request */ + private $request; + /** @var IUserSession */ + private $userSession; + /** @var ControllerMethodReflector */ + private $reflector; + /** @var Limiter */ + private $limiter; + + /** + * @param IRequest $request + * @param IUserSession $userSession + * @param ControllerMethodReflector $reflector + * @param Limiter $limiter + */ + public function __construct(IRequest $request, + IUserSession $userSession, + ControllerMethodReflector $reflector, + Limiter $limiter) { + $this->request = $request; + $this->userSession = $userSession; + $this->reflector = $reflector; + $this->limiter = $limiter; + } + + /** + * {@inheritDoc} + * @throws RateLimitExceededException + */ + public function beforeController($controller, $methodName) { + parent::beforeController($controller, $methodName); + + $anonLimit = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'limit'); + $anonPeriod = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'period'); + $userLimit = $this->reflector->getAnnotationParameter('UserRateThrottle', 'limit'); + $userPeriod = $this->reflector->getAnnotationParameter('UserRateThrottle', 'period'); + $rateLimitIdentifier = get_class($controller) . '::' . $methodName; + if ($userLimit !== '' && $userPeriod !== '' && $this->userSession->isLoggedIn()) { + $this->limiter->registerUserRequest( + $rateLimitIdentifier, + $userLimit, + $userPeriod, + $this->userSession->getUser() + ); + } elseif ($anonLimit !== '' && $anonPeriod !== '') { + $this->limiter->registerAnonRequest( + $rateLimitIdentifier, + $anonLimit, + $anonPeriod, + $this->request->getRemoteAddress() + ); + } + } + + /** + * {@inheritDoc} + */ + public function afterException($controller, $methodName, \Exception $exception) { + if ($exception instanceof RateLimitExceededException) { + if (stripos($this->request->getHeader('Accept'),'html') === false) { + $response = new JSONResponse( + [ + 'message' => $exception->getMessage(), + ], + $exception->getCode() + ); + } else { + $response = new TemplateResponse( + 'core', + '403', + [ + 'file' => $exception->getMessage() + ], + 'guest' + ); + $response->setStatus($exception->getCode()); + } + + return $response; + } + + throw $exception; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php new file mode 100644 index 0000000..12b0ef4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php @@ -0,0 +1,68 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Middleware\Security\Exceptions\ReloadExecutionException; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Middleware; +use OCP\ISession; +use OCP\IURLGenerator; + +/** + * Simple middleware to handle the clearing of the execution context. This will trigger + * a reload but if the session variable is set we properly redirect to the login page. + */ +class ReloadExecutionMiddleware extends Middleware { + /** @var ISession */ + private $session; + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(ISession $session, IURLGenerator $urlGenerator) { + $this->session = $session; + $this->urlGenerator = $urlGenerator; + } + + public function beforeController($controller, $methodName) { + if ($this->session->exists('clearingExecutionContexts')) { + throw new ReloadExecutionException(); + } + } + + public function afterException($controller, $methodName, \Exception $exception) { + if ($exception instanceof ReloadExecutionException) { + $this->session->remove('clearingExecutionContexts'); + + return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute( + 'core.login.showLoginForm', + ['clear' => true] // this param the the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers + )); + } + + return parent::afterException($controller, $methodName, $exception); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php new file mode 100644 index 0000000..7a358f6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -0,0 +1,108 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Middleware\Security\Exceptions\LaxSameSiteCookieFailedException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; + +class SameSiteCookieMiddleware extends Middleware { + + /** @var Request */ + private $request; + + /** @var ControllerMethodReflector */ + private $reflector; + + public function __construct(Request $request, + ControllerMethodReflector $reflector) { + $this->request = $request; + $this->reflector = $reflector; + } + + public function beforeController($controller, $methodName) { + $requestUri = $this->request->getScriptName(); + $processingScript = explode('/', $requestUri); + $processingScript = $processingScript[count($processingScript)-1]; + + if ($processingScript !== 'index.php') { + return; + } + + $noSSC = $this->reflector->hasAnnotation('NoSameSiteCookieRequired'); + if ($noSSC) { + return; + } + + if (!$this->request->passesLaxCookieCheck()) { + throw new LaxSameSiteCookieFailedException(); + } + } + + public function afterException($controller, $methodName, \Exception $exception) { + if ($exception instanceof LaxSameSiteCookieFailedException) { + $respone = new Response(); + $respone->setStatus(Http::STATUS_FOUND); + $respone->addHeader('Location', $this->request->getRequestUri()); + + $this->setSameSiteCookie(); + + return $respone; + } + + throw $exception; + } + + protected function setSameSiteCookie() { + $cookieParams = $this->request->getCookieParams(); + $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : ''; + $policies = [ + 'lax', + 'strict', + ]; + + // Append __Host to the cookie if it meets the requirements + $cookiePrefix = ''; + if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') { + $cookiePrefix = '__Host-'; + } + + foreach ($policies as $policy) { + header( + sprintf( + 'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s', + $cookiePrefix, + $policy, + $cookieParams['path'], + $policy + ), + false + ); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php new file mode 100644 index 0000000..76665f8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -0,0 +1,245 @@ + + * @author Bernhard Posselt + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Holger Hees + * @author Joas Schilling + * @author Julien Veyssier + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; +use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException; +use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; +use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; +use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; +use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\OCSController; +use OCP\IL10N; +use OCP\ILogger; +use OCP\INavigationManager; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\Util; + +/** + * Used to do all the authentication and checking stuff for a controller method + * It reads out the annotations of a controller method and checks which if + * security things should be checked and also handles errors in case a security + * check fails + */ +class SecurityMiddleware extends Middleware { + /** @var INavigationManager */ + private $navigationManager; + /** @var IRequest */ + private $request; + /** @var ControllerMethodReflector */ + private $reflector; + /** @var string */ + private $appName; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var ILogger */ + private $logger; + /** @var bool */ + private $isLoggedIn; + /** @var bool */ + private $isAdminUser; + /** @var bool */ + private $isSubAdmin; + /** @var IAppManager */ + private $appManager; + /** @var IL10N */ + private $l10n; + + public function __construct(IRequest $request, + ControllerMethodReflector $reflector, + INavigationManager $navigationManager, + IURLGenerator $urlGenerator, + ILogger $logger, + string $appName, + bool $isLoggedIn, + bool $isAdminUser, + bool $isSubAdmin, + IAppManager $appManager, + IL10N $l10n + ) { + $this->navigationManager = $navigationManager; + $this->request = $request; + $this->reflector = $reflector; + $this->appName = $appName; + $this->urlGenerator = $urlGenerator; + $this->logger = $logger; + $this->isLoggedIn = $isLoggedIn; + $this->isAdminUser = $isAdminUser; + $this->isSubAdmin = $isSubAdmin; + $this->appManager = $appManager; + $this->l10n = $l10n; + } + + /** + * This runs all the security checks before a method call. The + * security checks are determined by inspecting the controller method + * annotations + * @param Controller $controller the controller + * @param string $methodName the name of the method + * @throws SecurityException when a security check fails + * + * @suppress PhanUndeclaredClassConstant + */ + public function beforeController($controller, $methodName) { + + // this will set the current navigation entry of the app, use this only + // for normal HTML requests and not for AJAX requests + $this->navigationManager->setActiveEntry($this->appName); + + if (get_class($controller) === \OCA\Talk\Controller\PageController::class && $methodName === 'showCall') { + $this->navigationManager->setActiveEntry('spreed'); + } + + // security checks + $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); + if (!$isPublicPage) { + if (!$this->isLoggedIn) { + throw new NotLoggedInException(); + } + + if ($this->reflector->hasAnnotation('SubAdminRequired') + && !$this->isSubAdmin + && !$this->isAdminUser) { + throw new NotAdminException($this->l10n->t('Logged in user must be an admin or sub admin')); + } + if (!$this->reflector->hasAnnotation('SubAdminRequired') + && !$this->reflector->hasAnnotation('NoAdminRequired') + && !$this->isAdminUser) { + throw new NotAdminException($this->l10n->t('Logged in user must be an admin')); + } + } + + // Check for strict cookie requirement + if ($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) { + if (!$this->request->passesStrictCookieCheck()) { + throw new StrictCookieMissingException(); + } + } + // CSRF check - also registers the CSRF token since the session may be closed later + Util::callRegister(); + if (!$this->reflector->hasAnnotation('NoCSRFRequired')) { + /* + * Only allow the CSRF check to fail on OCS Requests. This kind of + * hacks around that we have no full token auth in place yet and we + * do want to offer CSRF checks for web requests. + * + * Additionally we allow Bearer authenticated requests to pass on OCS routes. + * This allows oauth apps (e.g. moodle) to use the OCS endpoints + */ + if (!$this->request->passesCSRFCheck() && !( + $controller instanceof OCSController && ( + $this->request->getHeader('OCS-APIREQUEST') === 'true' || + strpos($this->request->getHeader('Authorization'), 'Bearer ') === 0 + ) + )) { + throw new CrossSiteRequestForgeryException(); + } + } + + /** + * Checks if app is enabled (also includes a check whether user is allowed to access the resource) + * The getAppPath() check is here since components such as settings also use the AppFramework and + * therefore won't pass this check. + * If page is public, app does not need to be enabled for current user/visitor + */ + try { + $appPath = $this->appManager->getAppPath($this->appName); + } catch (AppPathNotFoundException $e) { + $appPath = false; + } + + if ($appPath !== false && !$isPublicPage && !$this->appManager->isEnabledForUser($this->appName)) { + throw new AppNotEnabledException(); + } + } + + /** + * If an SecurityException is being caught, ajax requests return a JSON error + * response and non ajax requests redirect to the index + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it can't handle it + * @return Response a Response object or null in case that the exception could not be handled + */ + public function afterException($controller, $methodName, \Exception $exception): Response { + if ($exception instanceof SecurityException) { + if ($exception instanceof StrictCookieMissingException) { + return new RedirectResponse(\OC::$WEBROOT . '/'); + } + if (stripos($this->request->getHeader('Accept'),'html') === false) { + $response = new JSONResponse( + ['message' => $exception->getMessage()], + $exception->getCode() + ); + } else { + if ($exception instanceof NotLoggedInException) { + $params = []; + if (isset($this->request->server['REQUEST_URI'])) { + $params['redirect_url'] = $this->request->server['REQUEST_URI']; + } + $url = $this->urlGenerator->linkToRoute('core.login.showLoginForm', $params); + $response = new RedirectResponse($url); + } else { + $response = new TemplateResponse('core', '403', ['message' => $exception->getMessage()], 'guest'); + $response->setStatus($exception->getCode()); + } + } + + $this->logger->logException($exception, [ + 'level' => ILogger::DEBUG, + 'app' => 'core', + ]); + return $response; + } + + throw $exception; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/SessionMiddleware.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/SessionMiddleware.php new file mode 100644 index 0000000..40b7723 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Middleware/SessionMiddleware.php @@ -0,0 +1,73 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; +use OCP\ISession; + +class SessionMiddleware extends Middleware { + + /** @var ControllerMethodReflector */ + private $reflector; + + /** @var ISession */ + private $session; + + public function __construct(ControllerMethodReflector $reflector, + ISession $session) { + $this->reflector = $reflector; + $this->session = $session; + } + + /** + * @param Controller $controller + * @param string $methodName + */ + public function beforeController($controller, $methodName) { + $useSession = $this->reflector->hasAnnotation('UseSession'); + if (!$useSession) { + $this->session->close(); + } + } + + /** + * @param Controller $controller + * @param string $methodName + * @param Response $response + * @return Response + */ + public function afterController($controller, $methodName, Response $response) { + $useSession = $this->reflector->hasAnnotation('UseSession'); + if ($useSession) { + $this->session->close(); + } + return $response; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/BaseResponse.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/BaseResponse.php new file mode 100644 index 0000000..55410c8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/BaseResponse.php @@ -0,0 +1,151 @@ + + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\OCS; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\Response; + +abstract class BaseResponse extends Response { + /** @var array */ + protected $data; + + /** @var string */ + protected $format; + + /** @var string */ + protected $statusMessage; + + /** @var int */ + protected $itemsCount; + + /** @var int */ + protected $itemsPerPage; + + /** + * BaseResponse constructor. + * + * @param DataResponse $dataResponse + * @param string $format + * @param string|null $statusMessage + * @param int|null $itemsCount + * @param int|null $itemsPerPage + */ + public function __construct(DataResponse $dataResponse, + $format = 'xml', + $statusMessage = null, + $itemsCount = null, + $itemsPerPage = null) { + parent::__construct(); + + $this->format = $format; + $this->statusMessage = $statusMessage; + $this->itemsCount = $itemsCount; + $this->itemsPerPage = $itemsPerPage; + + $this->data = $dataResponse->getData(); + + $this->setHeaders($dataResponse->getHeaders()); + $this->setStatus($dataResponse->getStatus()); + $this->setETag($dataResponse->getETag()); + $this->setLastModified($dataResponse->getLastModified()); + $this->setCookies($dataResponse->getCookies()); + + if ($format === 'json') { + $this->addHeader( + 'Content-Type', 'application/json; charset=utf-8' + ); + } else { + $this->addHeader( + 'Content-Type', 'application/xml; charset=utf-8' + ); + } + } + + /** + * @param string[] $meta + * @return string + */ + protected function renderResult(array $meta): string { + $status = $this->getStatus(); + if ($status === Http::STATUS_NO_CONTENT || + $status === Http::STATUS_NOT_MODIFIED || + ($status >= 100 && $status <= 199)) { + // Those status codes are not supposed to have a body: + // https://stackoverflow.com/q/8628725 + return ''; + } + + $response = [ + 'ocs' => [ + 'meta' => $meta, + 'data' => $this->data, + ], + ]; + + if ($this->format === 'json') { + return json_encode($response, JSON_HEX_TAG); + } + + $writer = new \XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + $this->toXML($response, $writer); + $writer->endDocument(); + return $writer->outputMemory(true); + } + + /** + * @param array $array + * @param \XMLWriter $writer + */ + protected function toXML(array $array, \XMLWriter $writer) { + foreach ($array as $k => $v) { + if (\is_string($k) && strpos($k, '@') === 0) { + $writer->writeAttribute(substr($k, 1), $v); + continue; + } + + if (\is_numeric($k)) { + $k = 'element'; + } + + if (\is_array($v)) { + $writer->startElement($k); + $this->toXML($v, $writer); + $writer->endElement(); + } else { + $writer->writeElement($k, $v); + } + } + } + + public function getOCSStatus() { + return parent::getStatus(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V1Response.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V1Response.php new file mode 100644 index 0000000..5a3e409 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V1Response.php @@ -0,0 +1,79 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\OCS; + +use OCP\API; +use OCP\AppFramework\Http; + +class V1Response extends BaseResponse { + + /** + * The V1 endpoint has very limited http status codes basically everything + * is status 200 except 401 + * + * @return int + */ + public function getStatus() { + $status = parent::getStatus(); + if ($status === Http::STATUS_FORBIDDEN || $status === API::RESPOND_UNAUTHORISED) { + return Http::STATUS_UNAUTHORIZED; + } + + return Http::STATUS_OK; + } + + /** + * In v1 all OK is 100 + * + * @return int + */ + public function getOCSStatus() { + $status = parent::getOCSStatus(); + + if ($status === Http::STATUS_OK) { + return 100; + } + + return $status; + } + + /** + * Construct the meta part of the response + * And then late the base class render + * + * @return string + */ + public function render() { + $meta = [ + 'status' => $this->getOCSStatus() === 100 ? 'ok' : 'failure', + 'statuscode' => $this->getOCSStatus(), + 'message' => $this->getOCSStatus() === 100 ? 'OK' : $this->statusMessage, + ]; + + $meta['totalitems'] = $this->itemsCount !== null ? (string)$this->itemsCount : ''; + $meta['itemsperpage'] = $this->itemsPerPage !== null ? (string)$this->itemsPerPage: ''; + + return $this->renderResult($meta); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V2Response.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V2Response.php new file mode 100644 index 0000000..b686326 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/OCS/V2Response.php @@ -0,0 +1,77 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\OCS; + +use OCP\API; +use OCP\AppFramework\Http; + +class V2Response extends BaseResponse { + + /** + * The V2 endpoint just passes on status codes. + * Of course we have to map the OCS specific codes to proper HTTP status codes + * + * @return int + */ + public function getStatus() { + $status = parent::getStatus(); + if ($status === API::RESPOND_UNAUTHORISED) { + return Http::STATUS_UNAUTHORIZED; + } elseif ($status === API::RESPOND_NOT_FOUND) { + return Http::STATUS_NOT_FOUND; + } elseif ($status === API::RESPOND_SERVER_ERROR || $status === API::RESPOND_UNKNOWN_ERROR) { + return Http::STATUS_INTERNAL_SERVER_ERROR; + } elseif ($status < 200 || $status > 600) { + return Http::STATUS_BAD_REQUEST; + } + + return $status; + } + + /** + * Construct the meta part of the response + * And then late the base class render + * + * @return string + */ + public function render() { + $status = parent::getStatus(); + + $meta = [ + 'status' => $status >= 200 && $status < 300 ? 'ok' : 'failure', + 'statuscode' => $this->getOCSStatus(), + 'message' => $status >= 200 && $status < 300 ? 'OK' : $this->statusMessage, + ]; + + if ($this->itemsCount !== null) { + $meta['totalitems'] = $this->itemsCount; + } + if ($this->itemsPerPage !== null) { + $meta['itemsperpage'] = $this->itemsPerPage; + } + + return $this->renderResult($meta); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteActionHandler.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteActionHandler.php new file mode 100644 index 0000000..46677b1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteActionHandler.php @@ -0,0 +1,49 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Routing; + +use OC\AppFramework\App; +use OC\AppFramework\DependencyInjection\DIContainer; + +class RouteActionHandler { + private $controllerName; + private $actionName; + private $container; + + /** + * @param string $controllerName + * @param string $actionName + */ + public function __construct(DIContainer $container, $controllerName, $actionName) { + $this->controllerName = $controllerName; + $this->actionName = $actionName; + $this->container = $container; + } + + public function __invoke($params) { + App::main($this->controllerName, $this->actionName, $this->container, $params); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteConfig.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteConfig.php new file mode 100644 index 0000000..1921ce6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Routing/RouteConfig.php @@ -0,0 +1,296 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Routing; + +use OC\AppFramework\DependencyInjection\DIContainer; +use OCP\Route\IRouter; + +/** + * Class RouteConfig + * @package OC\AppFramework\routing + */ +class RouteConfig { + /** @var DIContainer */ + private $container; + + /** @var IRouter */ + private $router; + + /** @var array */ + private $routes; + + /** @var string */ + private $appName; + + /** @var string[] */ + private $controllerNameCache = []; + + protected $rootUrlApps = [ + 'cloud_federation_api', + 'core', + 'files_sharing', + 'files', + 'settings', + 'spreed', + ]; + + /** + * @param \OC\AppFramework\DependencyInjection\DIContainer $container + * @param \OCP\Route\IRouter $router + * @param array $routes + * @internal param $appName + */ + public function __construct(DIContainer $container, IRouter $router, $routes) { + $this->routes = $routes; + $this->container = $container; + $this->router = $router; + $this->appName = $container['AppName']; + } + + /** + * The routes and resource will be registered to the \OCP\Route\IRouter + */ + public function register() { + + // parse simple + $this->processIndexRoutes($this->routes); + + // parse resources + $this->processIndexResources($this->routes); + + /* + * OCS routes go into a different collection + */ + $oldCollection = $this->router->getCurrentCollection(); + $this->router->useCollection($oldCollection . '.ocs'); + + // parse ocs simple routes + $this->processOCS($this->routes); + + // parse ocs simple routes + $this->processOCSResources($this->routes); + + $this->router->useCollection($oldCollection); + } + + private function processOCS(array $routes): void { + $ocsRoutes = $routes['ocs'] ?? []; + foreach ($ocsRoutes as $ocsRoute) { + $this->processRoute($ocsRoute, 'ocs.'); + } + } + + /** + * Creates one route base on the give configuration + * @param array $routes + * @throws \UnexpectedValueException + */ + private function processIndexRoutes(array $routes): void { + $simpleRoutes = $routes['routes'] ?? []; + foreach ($simpleRoutes as $simpleRoute) { + $this->processRoute($simpleRoute); + } + } + + protected function processRoute(array $route, string $routeNamePrefix = ''): void { + $name = $route['name']; + $postfix = $route['postfix'] ?? ''; + $root = $this->buildRootPrefix($route, $routeNamePrefix); + + $url = $root . '/' . ltrim($route['url'], '/'); + $verb = strtoupper($route['verb'] ?? 'GET'); + + $split = explode('#', $name, 2); + if (count($split) !== 2) { + throw new \UnexpectedValueException('Invalid route name'); + } + list($controller, $action) = $split; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($action); + + $routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix; + + $router = $this->router->create($routeName, $url) + ->method($verb); + + // optionally register requirements for route. This is used to + // tell the route parser how url parameters should be matched + if (array_key_exists('requirements', $route)) { + $router->requirements($route['requirements']); + } + + // optionally register defaults for route. This is used to + // tell the route parser how url parameters should be default valued + $defaults = []; + if (array_key_exists('defaults', $route)) { + $defaults = $route['defaults']; + } + + $defaults['caller'] = [$this->appName, $controllerName, $actionName]; + $router->defaults($defaults); + } + + /** + * For a given name and url restful OCS routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processOCSResources(array $routes): void { + $this->processResources($routes['ocs-resources'] ?? [], 'ocs.'); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processIndexResources(array $routes): void { + $this->processResources($routes['resources'] ?? []); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $resources + * @param string $routeNamePrefix + */ + protected function processResources(array $resources, string $routeNamePrefix = ''): void { + // declaration of all restful actions + $actions = [ + ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], + ['name' => 'show', 'verb' => 'GET'], + ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], + ['name' => 'update', 'verb' => 'PUT'], + ['name' => 'destroy', 'verb' => 'DELETE'], + ]; + + foreach ($resources as $resource => $config) { + $root = $this->buildRootPrefix($config, $routeNamePrefix); + + // the url parameter used as id to the resource + foreach ($actions as $action) { + $url = $root . '/' . ltrim($config['url'], '/'); + $method = $action['name']; + + $verb = strtoupper($action['verb'] ?? 'GET'); + $collectionAction = $action['on-collection'] ?? false; + if (!$collectionAction) { + $url .= '/{id}'; + } + if (isset($action['url-postfix'])) { + $url .= '/' . $action['url-postfix']; + } + + $controller = $resource; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($method); + + $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . strtolower($method); + + $route = $this->router->create($routeName, $url) + ->method($verb); + + $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]); + } + } + } + + private function buildRootPrefix(array $route, string $routeNamePrefix): string { + $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName; + $root = $route['root'] ?? $defaultRoot; + + if ($routeNamePrefix !== '') { + // In OCS all apps are whitelisted + return $root; + } + + if (!\in_array($this->appName, $this->rootUrlApps, true)) { + // Only allow root URLS for some apps + return $defaultRoot; + } + + return $root; + } + + /** + * Based on a given route name the controller name is generated + * @param string $controller + * @return string + */ + private function buildControllerName(string $controller): string { + if (!isset($this->controllerNameCache[$controller])) { + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + return $this->controllerNameCache[$controller]; + } + + /** + * Based on the action part of the route name the controller method name is generated + * @param string $action + * @return string + */ + private function buildActionName(string $action): string { + return $this->underScoreToCamelCase($action); + } + + /** + * Underscored strings are converted to camel case strings + * @param string $str + * @return string + */ + private function underScoreToCamelCase(string $str): string { + $pattern = '/_[a-z]?/'; + return preg_replace_callback( + $pattern, + function ($matches) { + return strtoupper(ltrim($matches[0], '_')); + }, + $str); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/ScopedPsrLogger.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/ScopedPsrLogger.php new file mode 100644 index 0000000..7eee729 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/ScopedPsrLogger.php @@ -0,0 +1,153 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework; + +use Psr\Log\LoggerInterface; +use function array_merge; + +class ScopedPsrLogger implements LoggerInterface { + + /** @var LoggerInterface */ + private $inner; + + /** @var string */ + private $appId; + + public function __construct(LoggerInterface $inner, + string $appId) { + $this->inner = $inner; + $this->appId = $appId; + } + + public function emergency($message, array $context = []) { + $this->inner->emergency( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function alert($message, array $context = []) { + $this->inner->alert( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function critical($message, array $context = []) { + $this->inner->critical( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function error($message, array $context = []) { + $this->inner->error( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function warning($message, array $context = []) { + $this->inner->warning( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function notice($message, array $context = []) { + $this->inner->notice( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function info($message, array $context = []) { + $this->inner->info( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function debug($message, array $context = []) { + $this->inner->debug( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } + + public function log($level, $message, array $context = []) { + $this->inner->log( + $message, + array_merge( + [ + 'app' => $this->appId, + ], + $context + ) + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/AppConfig.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/AppConfig.php new file mode 100644 index 0000000..37e73fb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/AppConfig.php @@ -0,0 +1,77 @@ + + * + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Services; + +use OCP\AppFramework\Services\IAppConfig; +use OCP\IConfig; + +class AppConfig implements IAppConfig { + + /** @var IConfig */ + private $config; + + /** @var string */ + private $appName; + + public function __construct(IConfig $config, string $appName) { + $this->config = $config; + $this->appName = $appName; + } + + public function getAppKeys(): array { + return $this->config->getAppKeys($this->appName); + } + + public function setAppValue(string $key, string $value): void { + $this->config->setAppValue($this->appName, $key, $value); + } + + public function getAppValue(string $key, string $default = ''): string { + return $this->config->getAppValue($this->appName, $key, $default); + } + + public function deleteAppValue(string $key): void { + $this->config->deleteAppValue($this->appName, $key); + } + + public function deleteAppValues(): void { + $this->config->deleteAppValues($this->appName); + } + + public function setUserValue(string $userId, string $key, string $value, ?string $preCondition = null): void { + $this->config->setUserValue($userId, $this->appName, $key, $value, $preCondition); + } + + public function getUserValue(string $userId, string $key, string $default = ''): string { + return $this->config->getUserValue($userId, $this->appName, $key, $default); + } + + public function deleteUserValue(string $userId, string $key): void { + $this->config->deleteUserValue($userId, $this->appName, $key); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/InitialState.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/InitialState.php new file mode 100644 index 0000000..25c7339 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Services/InitialState.php @@ -0,0 +1,51 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\AppFramework\Services; + +use OCP\AppFramework\Services\IInitialState; +use OCP\IInitialStateService; + +class InitialState implements IInitialState { + /** @var IInitialStateService */ + private $state; + + /** @var string */ + private $appName; + + public function __construct(IInitialStateService $state, string $appName) { + $this->state = $state; + $this->appName = $appName; + } + + public function provideInitialState(string $key, $data): void { + $this->state->provideInitialState($this->appName, $key, $data); + } + + public function provideLazyInitialState(string $key, \Closure $closure): void { + $this->state->provideLazyInitialState($this->appName, $key, $closure); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/ControllerMethodReflector.php new file mode 100644 index 0000000..7741797 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -0,0 +1,142 @@ + + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Olivier Paroz + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Utility; + +use OCP\AppFramework\Utility\IControllerMethodReflector; + +/** + * Reads and parses annotations from doc comments + */ +class ControllerMethodReflector implements IControllerMethodReflector { + public $annotations = []; + private $types = []; + private $parameters = []; + + /** + * @param object $object an object or classname + * @param string $method the method which we want to inspect + */ + public function reflect($object, string $method) { + $reflection = new \ReflectionMethod($object, $method); + $docs = $reflection->getDocComment(); + + if ($docs !== false) { + // extract everything prefixed by @ and first letter uppercase + preg_match_all('/^\h+\*\h+@(?P[A-Z]\w+)((?P.*))?$/m', $docs, $matches); + foreach ($matches['annotation'] as $key => $annontation) { + $annontation = strtolower($annontation); + $annotationValue = $matches['parameter'][$key]; + if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') { + $cutString = substr($annotationValue, 1, -1); + $cutString = str_replace(' ', '', $cutString); + $splittedArray = explode(',', $cutString); + foreach ($splittedArray as $annotationValues) { + list($key, $value) = explode('=', $annotationValues); + $this->annotations[$annontation][$key] = $value; + } + continue; + } + + $this->annotations[$annontation] = [$annotationValue]; + } + + // extract type parameter information + preg_match_all('/@param\h+(?P\w+)\h+\$(?P\w+)/', $docs, $matches); + $this->types = array_combine($matches['var'], $matches['type']); + } + + foreach ($reflection->getParameters() as $param) { + // extract type information from PHP 7 scalar types and prefer them over phpdoc annotations + $type = $param->getType(); + if ($type instanceof \ReflectionNamedType) { + $this->types[$param->getName()] = $type->getName(); + } + + $default = null; + if ($param->isOptional()) { + $default = $param->getDefaultValue(); + } + $this->parameters[$param->name] = $default; + } + } + + /** + * Inspects the PHPDoc parameters for types + * @param string $parameter the parameter whose type comments should be + * parsed + * @return string|null type in the type parameters (@param int $something) + * would return int or null if not existing + */ + public function getType(string $parameter) { + if (array_key_exists($parameter, $this->types)) { + return $this->types[$parameter]; + } + + return null; + } + + /** + * @return array the arguments of the method with key => default value + */ + public function getParameters(): array { + return $this->parameters; + } + + /** + * Check if a method contains an annotation + * @param string $name the name of the annotation + * @return bool true if the annotation is found + */ + public function hasAnnotation(string $name): bool { + $name = strtolower($name); + return array_key_exists($name, $this->annotations); + } + + /** + * Get optional annotation parameter by key + * + * @param string $name the name of the annotation + * @param string $key the string of the annotation + * @return string + */ + public function getAnnotationParameter(string $name, string $key): string { + $name = strtolower($name); + if (isset($this->annotations[$name][$key])) { + return $this->annotations[$name][$key]; + } + + return ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/SimpleContainer.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/SimpleContainer.php new file mode 100644 index 0000000..3bb275a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/SimpleContainer.php @@ -0,0 +1,219 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Utility; + +use ArrayAccess; +use Closure; +use OCP\AppFramework\QueryException; +use OCP\IContainer; +use Pimple\Container; +use Psr\Container\ContainerInterface; +use ReflectionClass; +use ReflectionException; +use ReflectionParameter; +use function class_exists; + +/** + * SimpleContainer is a simple implementation of a container on basis of Pimple + */ +class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { + + /** @var Container */ + private $container; + + public function __construct() { + $this->container = new Container(); + } + + public function get($id) { + return $this->query($id); + } + + public function has($id): bool { + // If a service is no registered but is an existing class, we can probably load it + return isset($this->container[$id]) || class_exists($id); + } + + /** + * @param ReflectionClass $class the class to instantiate + * @return \stdClass the created class + * @suppress PhanUndeclaredClassInstanceof + */ + private function buildClass(ReflectionClass $class) { + $constructor = $class->getConstructor(); + if ($constructor === null) { + return $class->newInstance(); + } + + return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) { + $parameterClass = $parameter->getClass(); + + // try to find out if it is a class or a simple parameter + if ($parameterClass === null) { + $resolveName = $parameter->getName(); + } else { + $resolveName = $parameterClass->name; + } + + try { + $builtIn = $parameter->hasType() && $parameter->getType()->isBuiltin(); + return $this->query($resolveName, !$builtIn); + } catch (QueryException $e) { + // Service not found, use the default value when available + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + if ($parameterClass !== null) { + $resolveName = $parameter->getName(); + return $this->query($resolveName); + } + + throw $e; + } + }, $constructor->getParameters())); + } + + public function resolve($name) { + $baseMsg = 'Could not resolve ' . $name . '!'; + try { + $class = new ReflectionClass($name); + if ($class->isInstantiable()) { + return $this->buildClass($class); + } else { + throw new QueryException($baseMsg . + ' Class can not be instantiated'); + } + } catch (ReflectionException $e) { + throw new QueryException($baseMsg . ' ' . $e->getMessage()); + } + } + + public function query(string $name, bool $autoload = true) { + $name = $this->sanitizeName($name); + if (isset($this->container[$name])) { + return $this->container[$name]; + } + + if ($autoload) { + $object = $this->resolve($name); + $this->registerService($name, function () use ($object) { + return $object; + }); + return $object; + } + + throw new QueryException('Could not resolve ' . $name . '!'); + } + + /** + * @param string $name + * @param mixed $value + */ + public function registerParameter($name, $value) { + $this[$name] = $value; + } + + /** + * The given closure is call the first time the given service is queried. + * The closure has to return the instance for the given service. + * Created instance will be cached in case $shared is true. + * + * @param string $name name of the service to register another backend for + * @param Closure $closure the closure to be called on service creation + * @param bool $shared + */ + public function registerService($name, Closure $closure, $shared = true) { + $wrapped = function () use ($closure) { + return $closure($this); + }; + $name = $this->sanitizeName($name); + if (isset($this[$name])) { + unset($this[$name]); + } + if ($shared) { + $this[$name] = $wrapped; + } else { + $this[$name] = $this->container->factory($wrapped); + } + } + + /** + * Shortcut for returning a service from a service under a different key, + * e.g. to tell the container to return a class when queried for an + * interface + * @param string $alias the alias that should be registered + * @param string $target the target that should be resolved instead + */ + public function registerAlias($alias, $target) { + $this->registerService($alias, function (ContainerInterface $container) use ($target) { + return $container->get($target); + }, false); + } + + /* + * @param string $name + * @return string + */ + protected function sanitizeName($name) { + if (isset($name[0]) && $name[0] === '\\') { + return ltrim($name, '\\'); + } + return $name; + } + + /** + * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::has + */ + public function offsetExists($id) { + return $this->container->offsetExists($id); + } + + /** + * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get + */ + public function offsetGet($id) { + return $this->container->offsetGet($id); + } + + /** + * @deprecated 20.0.0 use \OCP\IContainer::registerService + */ + public function offsetSet($id, $service) { + $this->container->offsetSet($id, $service); + } + + /** + * @deprecated 20.0.0 + */ + public function offsetUnset($offset) { + $this->container->offsetUnset($offset); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/TimeFactory.php b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/TimeFactory.php new file mode 100644 index 0000000..30ab9bd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/AppFramework/Utility/TimeFactory.php @@ -0,0 +1,55 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\AppFramework\Utility; + +use OCP\AppFramework\Utility\ITimeFactory; + +/** + * Needed to mock calls to time() + */ +class TimeFactory implements ITimeFactory { + + + /** + * @return int the result of a call to time() + */ + public function getTime(): int { + return time(); + } + + /** + * @param string $time + * @param \DateTimeZone $timezone + * @return \DateTime + * @since 15.0.0 + */ + public function getDateTime(string $time = 'now', \DateTimeZone $timezone = null): \DateTime { + return new \DateTime($time, $timezone); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Archive/Archive.php b/docker/overlays/nextcloud/html/lib/private/Archive/Archive.php new file mode 100644 index 0000000..ee27009 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Archive/Archive.php @@ -0,0 +1,140 @@ + + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Archive; + +abstract class Archive { + /** + * @param $source + */ + abstract public function __construct($source); + /** + * add an empty folder to the archive + * @param string $path + * @return bool + */ + abstract public function addFolder($path); + /** + * add a file to the archive + * @param string $path + * @param string $source either a local file or string data + * @return bool + */ + abstract public function addFile($path, $source=''); + /** + * rename a file or folder in the archive + * @param string $source + * @param string $dest + * @return bool + */ + abstract public function rename($source, $dest); + /** + * get the uncompressed size of a file in the archive + * @param string $path + * @return int + */ + abstract public function filesize($path); + /** + * get the last modified time of a file in the archive + * @param string $path + * @return int + */ + abstract public function mtime($path); + /** + * get the files in a folder + * @param string $path + * @return array + */ + abstract public function getFolder($path); + /** + * get all files in the archive + * @return array + */ + abstract public function getFiles(); + /** + * get the content of a file + * @param string $path + * @return string + */ + abstract public function getFile($path); + /** + * extract a single file from the archive + * @param string $path + * @param string $dest + * @return bool + */ + abstract public function extractFile($path, $dest); + /** + * extract the archive + * @param string $dest + * @return bool + */ + abstract public function extract($dest); + /** + * check if a file or folder exists in the archive + * @param string $path + * @return bool + */ + abstract public function fileExists($path); + /** + * remove a file or folder from the archive + * @param string $path + * @return bool + */ + abstract public function remove($path); + /** + * get a file handler + * @param string $path + * @param string $mode + * @return resource + */ + abstract public function getStream($path, $mode); + /** + * add a folder and all its content + * @param string $path + * @param string $source + */ + public function addRecursive($path, $source) { + $dh = opendir($source); + if (is_resource($dh)) { + $this->addFolder($path); + while (($file = readdir($dh)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + if (is_dir($source.'/'.$file)) { + $this->addRecursive($path.'/'.$file, $source.'/'.$file); + } else { + $this->addFile($path.'/'.$file, $source.'/'.$file); + } + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Archive/TAR.php b/docker/overlays/nextcloud/html/lib/private/Archive/TAR.php new file mode 100644 index 0000000..0d41032 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Archive/TAR.php @@ -0,0 +1,384 @@ + + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Remco Brenninkmeijer + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Archive; + +use Icewind\Streams\CallbackWrapper; + +class TAR extends Archive { + public const PLAIN = 0; + public const GZIP = 1; + public const BZIP = 2; + + private $fileList; + private $cachedHeaders; + + /** + * @var \Archive_Tar tar + */ + private $tar = null; + private $path; + + /** + * @param string $source + */ + public function __construct($source) { + $types = [null, 'gz', 'bz2']; + $this->path = $source; + $this->tar = new \Archive_Tar($source, $types[self::getTarType($source)]); + } + + /** + * try to detect the type of tar compression + * + * @param string $file + * @return integer + */ + public static function getTarType($file) { + if (strpos($file, '.')) { + $extension = substr($file, strrpos($file, '.')); + switch ($extension) { + case '.gz': + case '.tgz': + return self::GZIP; + case '.bz': + case '.bz2': + return self::BZIP; + case '.tar': + return self::PLAIN; + default: + return self::PLAIN; + } + } else { + return self::PLAIN; + } + } + + /** + * add an empty folder to the archive + * + * @param string $path + * @return bool + */ + public function addFolder($path) { + $tmpBase = \OC::$server->getTempManager()->getTemporaryFolder(); + $path = rtrim($path, '/') . '/'; + if ($this->fileExists($path)) { + return false; + } + $parts = explode('/', $path); + $folder = $tmpBase; + foreach ($parts as $part) { + $folder .= '/' . $part; + if (!is_dir($folder)) { + mkdir($folder); + } + } + $result = $this->tar->addModify([$tmpBase . $path], '', $tmpBase); + rmdir($tmpBase . $path); + $this->fileList = false; + $this->cachedHeaders = false; + return $result; + } + + /** + * add a file to the archive + * + * @param string $path + * @param string $source either a local file or string data + * @return bool + */ + public function addFile($path, $source = '') { + if ($this->fileExists($path)) { + $this->remove($path); + } + if ($source and $source[0] == '/' and file_exists($source)) { + $source = file_get_contents($source); + } + $result = $this->tar->addString($path, $source); + $this->fileList = false; + $this->cachedHeaders = false; + return $result; + } + + /** + * rename a file or folder in the archive + * + * @param string $source + * @param string $dest + * @return bool + */ + public function rename($source, $dest) { + //no proper way to delete, rename entire archive, rename file and remake archive + $tmp = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->tar->extract($tmp); + rename($tmp . $source, $tmp . $dest); + $this->tar = null; + unlink($this->path); + $types = [null, 'gz', 'bz']; + $this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]); + $this->tar->createModify([$tmp], '', $tmp . '/'); + $this->fileList = false; + $this->cachedHeaders = false; + return true; + } + + /** + * @param string $file + */ + private function getHeader($file) { + if (!$this->cachedHeaders) { + $this->cachedHeaders = $this->tar->listContent(); + } + foreach ($this->cachedHeaders as $header) { + if ($file == $header['filename'] + or $file . '/' == $header['filename'] + or '/' . $file . '/' == $header['filename'] + or '/' . $file == $header['filename'] + ) { + return $header; + } + } + return null; + } + + /** + * get the uncompressed size of a file in the archive + * + * @param string $path + * @return int + */ + public function filesize($path) { + $stat = $this->getHeader($path); + return $stat['size']; + } + + /** + * get the last modified time of a file in the archive + * + * @param string $path + * @return int + */ + public function mtime($path) { + $stat = $this->getHeader($path); + return $stat['mtime']; + } + + /** + * get the files in a folder + * + * @param string $path + * @return array + */ + public function getFolder($path) { + $files = $this->getFiles(); + $folderContent = []; + $pathLength = strlen($path); + foreach ($files as $file) { + if ($file[0] == '/') { + $file = substr($file, 1); + } + if (substr($file, 0, $pathLength) == $path and $file != $path) { + $result = substr($file, $pathLength); + if ($pos = strpos($result, '/')) { + $result = substr($result, 0, $pos + 1); + } + if (array_search($result, $folderContent) === false) { + $folderContent[] = $result; + } + } + } + return $folderContent; + } + + /** + * get all files in the archive + * + * @return array + */ + public function getFiles() { + if ($this->fileList) { + return $this->fileList; + } + if (!$this->cachedHeaders) { + $this->cachedHeaders = $this->tar->listContent(); + } + $files = []; + foreach ($this->cachedHeaders as $header) { + $files[] = $header['filename']; + } + $this->fileList = $files; + return $files; + } + + /** + * get the content of a file + * + * @param string $path + * @return string + */ + public function getFile($path) { + return $this->tar->extractInString($path); + } + + /** + * extract a single file from the archive + * + * @param string $path + * @param string $dest + * @return bool + */ + public function extractFile($path, $dest) { + $tmp = \OC::$server->getTempManager()->getTemporaryFolder(); + if (!$this->fileExists($path)) { + return false; + } + if ($this->fileExists('/' . $path)) { + $success = $this->tar->extractList(['/' . $path], $tmp); + } else { + $success = $this->tar->extractList([$path], $tmp); + } + if ($success) { + rename($tmp . $path, $dest); + } + \OCP\Files::rmdirr($tmp); + return $success; + } + + /** + * extract the archive + * + * @param string $dest + * @return bool + */ + public function extract($dest) { + return $this->tar->extract($dest); + } + + /** + * check if a file or folder exists in the archive + * + * @param string $path + * @return bool + */ + public function fileExists($path) { + $files = $this->getFiles(); + if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) { + return true; + } else { + $folderPath = rtrim($path, '/') . '/'; + $pathLength = strlen($folderPath); + foreach ($files as $file) { + if (strlen($file) > $pathLength and substr($file, 0, $pathLength) == $folderPath) { + return true; + } + } + } + if ($path[0] != '/') { //not all programs agree on the use of a leading / + return $this->fileExists('/' . $path); + } else { + return false; + } + } + + /** + * remove a file or folder from the archive + * + * @param string $path + * @return bool + */ + public function remove($path) { + if (!$this->fileExists($path)) { + return false; + } + $this->fileList = false; + $this->cachedHeaders = false; + //no proper way to delete, extract entire archive, delete file and remake archive + $tmp = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->tar->extract($tmp); + \OCP\Files::rmdirr($tmp . $path); + $this->tar = null; + unlink($this->path); + $this->reopen(); + $this->tar->createModify([$tmp], '', $tmp); + return true; + } + + /** + * get a file handler + * + * @param string $path + * @param string $mode + * @return resource + */ + public function getStream($path, $mode) { + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + if ($this->fileExists($path)) { + $this->extractFile($path, $tmpFile); + } elseif ($mode == 'r' or $mode == 'rb') { + return false; + } + if ($mode == 'r' or $mode == 'rb') { + return fopen($tmpFile, $mode); + } else { + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + $this->writeBack($tmpFile, $path); + }); + } + } + + /** + * write back temporary files + */ + public function writeBack($tmpFile, $path) { + $this->addFile($path, $tmpFile); + unlink($tmpFile); + } + + /** + * reopen the archive to ensure everything is written + */ + private function reopen() { + if ($this->tar) { + $this->tar->_close(); + $this->tar = null; + } + $types = [null, 'gz', 'bz']; + $this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Archive/ZIP.php b/docker/overlays/nextcloud/html/lib/private/Archive/ZIP.php new file mode 100644 index 0000000..31aea42 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Archive/ZIP.php @@ -0,0 +1,234 @@ + + * @author Bart Visscher + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Archive; + +use Icewind\Streams\CallbackWrapper; +use OCP\ILogger; + +class ZIP extends Archive { + /** + * @var \ZipArchive zip + */ + private $zip=null; + private $path; + + /** + * @param string $source + */ + public function __construct($source) { + $this->path=$source; + $this->zip=new \ZipArchive(); + if ($this->zip->open($source, \ZipArchive::CREATE)) { + } else { + \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, ILogger::WARN); + } + } + /** + * add an empty folder to the archive + * @param string $path + * @return bool + */ + public function addFolder($path) { + return $this->zip->addEmptyDir($path); + } + /** + * add a file to the archive + * @param string $path + * @param string $source either a local file or string data + * @return bool + */ + public function addFile($path, $source='') { + if ($source and $source[0]=='/' and file_exists($source)) { + $result=$this->zip->addFile($source, $path); + } else { + $result=$this->zip->addFromString($path, $source); + } + if ($result) { + $this->zip->close();//close and reopen to save the zip + $this->zip->open($this->path); + } + return $result; + } + /** + * rename a file or folder in the archive + * @param string $source + * @param string $dest + * @return boolean|null + */ + public function rename($source, $dest) { + $source=$this->stripPath($source); + $dest=$this->stripPath($dest); + $this->zip->renameName($source, $dest); + } + /** + * get the uncompressed size of a file in the archive + * @param string $path + * @return int + */ + public function filesize($path) { + $stat=$this->zip->statName($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string $path + * @return int + */ + public function mtime($path) { + return filemtime($this->path); + } + /** + * get the files in a folder + * @param string $path + * @return array + */ + public function getFolder($path) { + $files=$this->getFiles(); + $folderContent=[]; + $pathLength=strlen($path); + foreach ($files as $file) { + if (substr($file, 0, $pathLength)==$path and $file!=$path) { + if (strrpos(substr($file, 0, -1), '/')<=$pathLength) { + $folderContent[]=substr($file, $pathLength); + } + } + } + return $folderContent; + } + /** + * get all files in the archive + * @return array + */ + public function getFiles() { + $fileCount=$this->zip->numFiles; + $files=[]; + for ($i=0;$i<$fileCount;$i++) { + $files[]=$this->zip->getNameIndex($i); + } + return $files; + } + /** + * get the content of a file + * @param string $path + * @return string + */ + public function getFile($path) { + return $this->zip->getFromName($path); + } + /** + * extract a single file from the archive + * @param string $path + * @param string $dest + * @return boolean|null + */ + public function extractFile($path, $dest) { + $fp = $this->zip->getStream($path); + file_put_contents($dest, $fp); + } + /** + * extract the archive + * @param string $dest + * @return bool + */ + public function extract($dest) { + return $this->zip->extractTo($dest); + } + /** + * check if a file or folder exists in the archive + * @param string $path + * @return bool + */ + public function fileExists($path) { + return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); + } + /** + * remove a file or folder from the archive + * @param string $path + * @return bool + */ + public function remove($path) { + if ($this->fileExists($path.'/')) { + return $this->zip->deleteName($path.'/'); + } else { + return $this->zip->deleteName($path); + } + } + /** + * get a file handler + * @param string $path + * @param string $mode + * @return resource + */ + public function getStream($path, $mode) { + if ($mode=='r' or $mode=='rb') { + return $this->zip->getStream($path); + } else { + //since we can't directly get a writable stream, + //make a temp copy of the file and put it back + //in the archive when the stream is closed + if (strrpos($path, '.')!==false) { + $ext=substr($path, strrpos($path, '.')); + } else { + $ext=''; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + if ($this->fileExists($path)) { + $this->extractFile($path, $tmpFile); + } + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + $this->writeBack($tmpFile, $path); + }); + } + } + + /** + * write back temporary files + */ + public function writeBack($tmpFile, $path) { + $this->addFile($path, $tmpFile); + unlink($tmpFile); + } + + /** + * @param string $path + * @return string + */ + private function stripPath($path) { + if (!$path || $path[0]=='/') { + return substr($path, 1); + } else { + return $path; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Events/ARemoteWipeEvent.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/ARemoteWipeEvent.php new file mode 100644 index 0000000..8d28cc5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/ARemoteWipeEvent.php @@ -0,0 +1,45 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Events; + +use OC\Authentication\Token\IToken; +use OCP\EventDispatcher\Event; + +abstract class ARemoteWipeEvent extends Event { + + /** @var IToken */ + private $token; + + public function __construct(IToken $token) { + parent::__construct(); + $this->token = $token; + } + + public function getToken(): IToken { + return $this->token; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Events/LoginFailed.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/LoginFailed.php new file mode 100644 index 0000000..7b10756 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/LoginFailed.php @@ -0,0 +1,45 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Events; + +use OCP\EventDispatcher\Event; + +class LoginFailed extends Event { + + /** @var string */ + private $loginName; + + public function __construct(string $loginName) { + parent::__construct(); + + $this->loginName = $loginName; + } + + public function getLoginName(): string { + return $this->loginName; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeFinished.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeFinished.php new file mode 100644 index 0000000..d50b7d2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeFinished.php @@ -0,0 +1,30 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Events; + +class RemoteWipeFinished extends ARemoteWipeEvent { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeStarted.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeStarted.php new file mode 100644 index 0000000..89b5605 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Events/RemoteWipeStarted.php @@ -0,0 +1,30 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Events; + +class RemoteWipeStarted extends ARemoteWipeEvent { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/ExpiredTokenException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/ExpiredTokenException.php new file mode 100644 index 0000000..3f50aab --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/ExpiredTokenException.php @@ -0,0 +1,44 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Exceptions; + +use OC\Authentication\Token\IToken; + +class ExpiredTokenException extends InvalidTokenException { + /** @var IToken */ + private $token; + + public function __construct(IToken $token) { + parent::__construct(); + + $this->token = $token; + } + + public function getToken(): IToken { + return $this->token; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidProviderException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidProviderException.php new file mode 100644 index 0000000..9700965 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidProviderException.php @@ -0,0 +1,36 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; +use Throwable; + +class InvalidProviderException extends Exception { + public function __construct(string $providerId, Throwable $previous = null) { + parent::__construct("The provider '$providerId' does not exist'", 0, $previous); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidTokenException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidTokenException.php new file mode 100644 index 0000000..000d6de --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/InvalidTokenException.php @@ -0,0 +1,31 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class InvalidTokenException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/LoginRequiredException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/LoginRequiredException.php new file mode 100644 index 0000000..9d9ce05 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/LoginRequiredException.php @@ -0,0 +1,28 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class LoginRequiredException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php new file mode 100644 index 0000000..02ddd06 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php @@ -0,0 +1,28 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class PasswordLoginForbiddenException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordlessTokenException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordlessTokenException.php new file mode 100644 index 0000000..5c59dd3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/PasswordlessTokenException.php @@ -0,0 +1,28 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class PasswordlessTokenException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php new file mode 100644 index 0000000..2850d3d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php @@ -0,0 +1,30 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Exceptions; + +class TokenPasswordExpiredException extends ExpiredTokenException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php new file mode 100644 index 0000000..6db87b3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php @@ -0,0 +1,28 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class TwoFactorAuthRequiredException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php new file mode 100644 index 0000000..878bf59 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php @@ -0,0 +1,28 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class UserAlreadyLoggedInException extends Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/WipeTokenException.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/WipeTokenException.php new file mode 100644 index 0000000..2ace043 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Exceptions/WipeTokenException.php @@ -0,0 +1,44 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Exceptions; + +use OC\Authentication\Token\IToken; + +class WipeTokenException extends InvalidTokenException { + /** @var IToken */ + private $token; + + public function __construct(IToken $token) { + parent::__construct(); + + $this->token = $token; + } + + public function getToken(): IToken { + return $this->token; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/LoginFailedListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/LoginFailedListener.php new file mode 100644 index 0000000..19f0b92 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/LoginFailedListener.php @@ -0,0 +1,69 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\Events\LoginFailed; +use OCP\Authentication\Events\LoginFailedEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\EventDispatcher\IEventListener; +use OCP\IUserManager; +use OCP\Util; + +/** + * @template-implements IEventListener<\OC\Authentication\Events\LoginFailed> + */ +class LoginFailedListener implements IEventListener { + + /** @var IEventDispatcher */ + private $dispatcher; + + /** @var IUserManager */ + private $userManager; + + public function __construct(IEventDispatcher $dispatcher, IUserManager $userManager) { + $this->dispatcher = $dispatcher; + $this->userManager = $userManager; + } + + public function handle(Event $event): void { + if (!($event instanceof LoginFailed)) { + return; + } + + $uid = $event->getLoginName(); + Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + ['uid' => &$uid] + ); + if ($this->userManager->userExists($uid)) { + $this->dispatcher->dispatchTyped(new LoginFailedEvent($uid)); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php new file mode 100644 index 0000000..76daebd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php @@ -0,0 +1,82 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use BadMethodCallException; +use OC\Authentication\Events\RemoteWipeFinished; +use OC\Authentication\Events\RemoteWipeStarted; +use OC\Authentication\Token\IToken; +use OCP\Activity\IManager as IActvityManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\ILogger; + +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ +class RemoteWipeActivityListener implements IEventListener { + + /** @var IActvityManager */ + private $activityManager; + + /** @var ILogger */ + private $logger; + + public function __construct(IActvityManager $activityManager, + ILogger $logger) { + $this->activityManager = $activityManager; + $this->logger = $logger; + } + + public function handle(Event $event): void { + if ($event instanceof RemoteWipeStarted) { + $this->publishActivity('remote_wipe_start', $event->getToken()); + } elseif ($event instanceof RemoteWipeFinished) { + $this->publishActivity('remote_wipe_finish', $event->getToken()); + } + } + + private function publishActivity(string $event, IToken $token): void { + $activity = $this->activityManager->generateEvent(); + $activity->setApp('core') + ->setType('security') + ->setAuthor($token->getUID()) + ->setAffectedUser($token->getUID()) + ->setSubject($event, [ + 'name' => $token->getName(), + ]); + try { + $this->activityManager->publish($activity); + } catch (BadMethodCallException $e) { + $this->logger->logException($e, [ + 'app' => 'core', + 'level' => ILogger::WARN, + 'message' => 'could not publish activity', + ]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php new file mode 100644 index 0000000..adde844 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php @@ -0,0 +1,176 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use Exception; +use OC\Authentication\Events\RemoteWipeFinished; +use OC\Authentication\Events\RemoteWipeStarted; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; +use OCP\L10N\IFactory as IL10nFactory; +use OCP\Mail\IMailer; +use OCP\Mail\IMessage; +use function substr; + +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ +class RemoteWipeEmailListener implements IEventListener { + + /** @var IMailer */ + private $mailer; + + /** @var IUserManager */ + private $userManager; + + /** @var IL10N */ + private $l10n; + + /** @var ILogger */ + private $logger; + + public function __construct(IMailer $mailer, + IUserManager $userManager, + IL10nFactory $l10nFactory, + ILogger $logger) { + $this->mailer = $mailer; + $this->userManager = $userManager; + $this->l10n = $l10nFactory->get('core'); + $this->logger = $logger; + } + + /** + * @param Event $event + */ + public function handle(Event $event): void { + if ($event instanceof RemoteWipeStarted) { + $uid = $event->getToken()->getUID(); + $user = $this->userManager->get($uid); + if ($user === null) { + $this->logger->warning("not sending a wipe started email because user <$uid> does not exist (anymore)"); + return; + } + if ($user->getEMailAddress() === null) { + $this->logger->info("not sending a wipe started email because user <$uid> has no email set"); + return; + } + + try { + $this->mailer->send( + $this->getWipingStartedMessage($event, $user) + ); + } catch (Exception $e) { + $this->logger->logException($e, [ + 'message' => "Could not send remote wipe started email to <$uid>", + 'level' => ILogger::ERROR, + ]); + } + } elseif ($event instanceof RemoteWipeFinished) { + $uid = $event->getToken()->getUID(); + $user = $this->userManager->get($uid); + if ($user === null) { + $this->logger->warning("not sending a wipe finished email because user <$uid> does not exist (anymore)"); + return; + } + if ($user->getEMailAddress() === null) { + $this->logger->info("not sending a wipe finished email because user <$uid> has no email set"); + return; + } + + try { + $this->mailer->send( + $this->getWipingFinishedMessage($event, $user) + ); + } catch (Exception $e) { + $this->logger->logException($e, [ + 'message' => "Could not send remote wipe finished email to <$uid>", + 'level' => ILogger::ERROR, + ]); + } + } + } + + private function getWipingStartedMessage(RemoteWipeStarted $event, IUser $user): IMessage { + $message = $this->mailer->createMessage(); + $emailTemplate = $this->mailer->createEMailTemplate('auth.RemoteWipeStarted'); + $plainHeading = $this->l10n->t('Wiping of device %s has started', [$event->getToken()->getName()]); + $htmlHeading = $this->l10n->t('Wiping of device »%s« has started', [$event->getToken()->getName()]); + $emailTemplate->setSubject( + $this->l10n->t( + '»%s« started remote wipe', + [ + substr($event->getToken()->getName(), 0, 15) + ] + ) + ); + $emailTemplate->addHeader(); + $emailTemplate->addHeading( + $htmlHeading, + $plainHeading + ); + $emailTemplate->addBodyText( + $this->l10n->t('Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished', [$event->getToken()->getName()]) + ); + $emailTemplate->addFooter(); + $message->setTo([$user->getEMailAddress()]); + $message->useTemplate($emailTemplate); + + return $message; + } + + private function getWipingFinishedMessage(RemoteWipeFinished $event, IUser $user): IMessage { + $message = $this->mailer->createMessage(); + $emailTemplate = $this->mailer->createEMailTemplate('auth.RemoteWipeFinished'); + $plainHeading = $this->l10n->t('Wiping of device %s has finished', [$event->getToken()->getName()]); + $htmlHeading = $this->l10n->t('Wiping of device »%s« has finished', [$event->getToken()->getName()]); + $emailTemplate->setSubject( + $this->l10n->t( + '»%s« finished remote wipe', + [ + substr($event->getToken()->getName(), 0, 15) + ] + ) + ); + $emailTemplate->addHeader(); + $emailTemplate->addHeading( + $htmlHeading, + $plainHeading + ); + $emailTemplate->addBodyText( + $this->l10n->t('Device or application »%s« has finished the remote wipe process.', [$event->getToken()->getName()]) + ); + $emailTemplate->addFooter(); + $message->setTo([$user->getEMailAddress()]); + $message->useTemplate($emailTemplate); + + return $message; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php new file mode 100644 index 0000000..965fb29 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php @@ -0,0 +1,74 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\Events\RemoteWipeFinished; +use OC\Authentication\Events\RemoteWipeStarted; +use OC\Authentication\Token\IToken; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Notification\IManager as INotificationManager; + +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ +class RemoteWipeNotificationsListener implements IEventListener { + + /** @var INotificationManager */ + private $notificationManager; + + /** @var ITimeFactory */ + private $timeFactory; + + public function __construct(INotificationManager $notificationManager, + ITimeFactory $timeFactory) { + $this->notificationManager = $notificationManager; + $this->timeFactory = $timeFactory; + } + + public function handle(Event $event): void { + if ($event instanceof RemoteWipeStarted) { + $this->sendNotification('remote_wipe_start', $event->getToken()); + } elseif ($event instanceof RemoteWipeFinished) { + $this->sendNotification('remote_wipe_finish', $event->getToken()); + } + } + + private function sendNotification(string $event, IToken $token): void { + $notification = $this->notificationManager->createNotification(); + $notification->setApp('auth') + ->setUser($token->getUID()) + ->setDateTime($this->timeFactory->getDateTime()) + ->setObject('token', (string) $token->getId()) + ->setSubject($event, [ + 'name' => $token->getName(), + ]); + $this->notificationManager->notify($notification); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php new file mode 100644 index 0000000..057568f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php @@ -0,0 +1,53 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\TwoFactorAuth\Registry; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\UserDeletedEvent; + +/** + * @template-implements IEventListener<\OCP\User\Events\UserDeletedEvent> + */ +class UserDeletedStoreCleanupListener implements IEventListener { + + /** @var Registry */ + private $registry; + + public function __construct(Registry $registry) { + $this->registry = $registry; + } + + public function handle(Event $event): void { + if (!($event instanceof UserDeletedEvent)) { + return; + } + + $this->registry->deleteUserData($event->getUser()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php new file mode 100644 index 0000000..4406294 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php @@ -0,0 +1,76 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\Token\Manager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\ILogger; +use OCP\User\Events\UserDeletedEvent; +use Throwable; + +/** + * @template-implements IEventListener<\OCP\User\Events\UserDeletedEvent> + */ +class UserDeletedTokenCleanupListener implements IEventListener { + + /** @var Manager */ + private $manager; + + /** @var ILogger */ + private $logger; + + public function __construct(Manager $manager, + ILogger $logger) { + $this->manager = $manager; + $this->logger = $logger; + } + + public function handle(Event $event): void { + if (!($event instanceof UserDeletedEvent)) { + // Unrelated + return; + } + + /** + * Catch any exception during this process as any failure here shouldn't block the + * user deletion. + */ + try { + $uid = $event->getUser()->getUID(); + $tokens = $this->manager->getTokenByUser($uid); + foreach ($tokens as $token) { + $this->manager->invalidateTokenById($uid, $token->getId()); + } + } catch (Throwable $e) { + $this->logger->logException($e, [ + 'message' => 'Could not clean up auth tokens after user deletion: ' . $e->getMessage(), + 'error' => ILogger::ERROR, + ]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserLoggedInListener.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserLoggedInListener.php new file mode 100644 index 0000000..9d90f09 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Listeners/UserLoggedInListener.php @@ -0,0 +1,58 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\Token\Manager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\PostLoginEvent; + +/** + * @template-implements IEventListener<\OCP\User\Events\PostLoginEvent> + */ +class UserLoggedInListener implements IEventListener { + + /** @var Manager */ + private $manager; + + public function __construct(Manager $manager) { + $this->manager = $manager; + } + + public function handle(Event $event): void { + if (!($event instanceof PostLoginEvent)) { + return; + } + + // If this is already a token login there is nothing to do + if ($event->isTokenLogin()) { + return; + } + + $this->manager->updatePasswords($event->getUser()->getUID(), $event->getPassword()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ALoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ALoginCommand.php new file mode 100644 index 0000000..effc9ad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ALoginCommand.php @@ -0,0 +1,47 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +abstract class ALoginCommand { + + /** @var ALoginCommand */ + protected $next; + + public function setNext(ALoginCommand $next): ALoginCommand { + $this->next = $next; + return $next; + } + + protected function processNextOrFinishSuccessfully(LoginData $loginData): LoginResult { + if ($this->next !== null) { + return $this->next->process($loginData); + } else { + return LoginResult::success($loginData); + } + } + + abstract public function process(LoginData $loginData): LoginResult; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/Chain.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/Chain.php new file mode 100644 index 0000000..1fc94fe --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/Chain.php @@ -0,0 +1,110 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +class Chain { + + /** @var PreLoginHookCommand */ + private $preLoginHookCommand; + + /** @var UserDisabledCheckCommand */ + private $userDisabledCheckCommand; + + /** @var UidLoginCommand */ + private $uidLoginCommand; + + /** @var EmailLoginCommand */ + private $emailLoginCommand; + + /** @var LoggedInCheckCommand */ + private $loggedInCheckCommand; + + /** @var CompleteLoginCommand */ + private $completeLoginCommand; + + /** @var CreateSessionTokenCommand */ + private $createSessionTokenCommand; + + /** @var ClearLostPasswordTokensCommand */ + private $clearLostPasswordTokensCommand; + + /** @var UpdateLastPasswordConfirmCommand */ + private $updateLastPasswordConfirmCommand; + + /** @var SetUserTimezoneCommand */ + private $setUserTimezoneCommand; + + /** @var TwoFactorCommand */ + private $twoFactorCommand; + + /** @var FinishRememberedLoginCommand */ + private $finishRememberedLoginCommand; + + public function __construct(PreLoginHookCommand $preLoginHookCommand, + UserDisabledCheckCommand $userDisabledCheckCommand, + UidLoginCommand $uidLoginCommand, + EmailLoginCommand $emailLoginCommand, + LoggedInCheckCommand $loggedInCheckCommand, + CompleteLoginCommand $completeLoginCommand, + CreateSessionTokenCommand $createSessionTokenCommand, + ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, + UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, + SetUserTimezoneCommand $setUserTimezoneCommand, + TwoFactorCommand $twoFactorCommand, + FinishRememberedLoginCommand $finishRememberedLoginCommand + ) { + $this->preLoginHookCommand = $preLoginHookCommand; + $this->userDisabledCheckCommand = $userDisabledCheckCommand; + $this->uidLoginCommand = $uidLoginCommand; + $this->emailLoginCommand = $emailLoginCommand; + $this->loggedInCheckCommand = $loggedInCheckCommand; + $this->completeLoginCommand = $completeLoginCommand; + $this->createSessionTokenCommand = $createSessionTokenCommand; + $this->clearLostPasswordTokensCommand = $clearLostPasswordTokensCommand; + $this->updateLastPasswordConfirmCommand = $updateLastPasswordConfirmCommand; + $this->setUserTimezoneCommand = $setUserTimezoneCommand; + $this->twoFactorCommand = $twoFactorCommand; + $this->finishRememberedLoginCommand = $finishRememberedLoginCommand; + } + + public function process(LoginData $loginData): LoginResult { + $chain = $this->preLoginHookCommand; + $chain + ->setNext($this->userDisabledCheckCommand) + ->setNext($this->uidLoginCommand) + ->setNext($this->emailLoginCommand) + ->setNext($this->loggedInCheckCommand) + ->setNext($this->completeLoginCommand) + ->setNext($this->createSessionTokenCommand) + ->setNext($this->clearLostPasswordTokensCommand) + ->setNext($this->updateLastPasswordConfirmCommand) + ->setNext($this->setUserTimezoneCommand) + ->setNext($this->twoFactorCommand) + ->setNext($this->finishRememberedLoginCommand); + + return $chain->process($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php new file mode 100644 index 0000000..f4e1ad1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/ClearLostPasswordTokensCommand.php @@ -0,0 +1,51 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OCP\IConfig; + +class ClearLostPasswordTokensCommand extends ALoginCommand { + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * User has successfully logged in, now remove the password reset link, when it is available + */ + public function process(LoginData $loginData): LoginResult { + $this->config->deleteUserValue( + $loginData->getUser()->getUID(), + 'core', + 'lostpassword' + ); + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CompleteLoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CompleteLoginCommand.php new file mode 100644 index 0000000..d0cddba --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CompleteLoginCommand.php @@ -0,0 +1,50 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\User\Session; + +class CompleteLoginCommand extends ALoginCommand { + + /** @var Session */ + private $userSession; + + public function __construct(Session $userSession) { + $this->userSession = $userSession; + } + + public function process(LoginData $loginData): LoginResult { + $this->userSession->completeLogin( + $loginData->getUser(), + [ + 'loginName' => $loginData->getUsername(), + 'password' => $loginData->getPassword(), + ] + ); + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CreateSessionTokenCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CreateSessionTokenCommand.php new file mode 100644 index 0000000..05b6c27 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/CreateSessionTokenCommand.php @@ -0,0 +1,82 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\Authentication\Token\IToken; +use OC\User\Session; +use OCP\IConfig; + +class CreateSessionTokenCommand extends ALoginCommand { + + /** @var IConfig */ + private $config; + + /** @var Session */ + private $userSession; + + public function __construct(IConfig $config, + Session $userSession) { + $this->config = $config; + $this->userSession = $userSession; + } + + public function process(LoginData $loginData): LoginResult { + $tokenType = IToken::REMEMBER; + if ((int)$this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15) === 0) { + $loginData->setRememberLogin(false); + $tokenType = IToken::DO_NOT_REMEMBER; + } + + if ($loginData->getPassword() === '') { + $this->userSession->createSessionToken( + $loginData->getRequest(), + $loginData->getUser()->getUID(), + $loginData->getUsername(), + null, + $tokenType + ); + $this->userSession->updateTokens( + $loginData->getUser()->getUID(), + '' + ); + } else { + $this->userSession->createSessionToken( + $loginData->getRequest(), + $loginData->getUser()->getUID(), + $loginData->getUsername(), + $loginData->getPassword(), + $tokenType + ); + $this->userSession->updateTokens( + $loginData->getUser()->getUID(), + $loginData->getPassword() + ); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/EmailLoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/EmailLoginCommand.php new file mode 100644 index 0000000..24b5a00 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/EmailLoginCommand.php @@ -0,0 +1,60 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OCP\IUserManager; + +class EmailLoginCommand extends ALoginCommand { + + /** @var IUserManager */ + private $userManager; + + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + } + + public function process(LoginData $loginData): LoginResult { + if ($loginData->getUser() === false) { + $users = $this->userManager->getByEmail($loginData->getUsername()); + // we only allow login by email if unique + if (count($users) === 1) { + $username = $users[0]->getUID(); + if ($username !== $loginData->getUsername()) { + $user = $this->userManager->checkPassword( + $username, + $loginData->getPassword() + ); + if ($user !== false) { + $loginData->setUser($user); + $loginData->setUsername($username); + } + } + } + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/FinishRememberedLoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/FinishRememberedLoginCommand.php new file mode 100644 index 0000000..48d9e51 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/FinishRememberedLoginCommand.php @@ -0,0 +1,51 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\User\Session; +use OCP\IConfig; + +class FinishRememberedLoginCommand extends ALoginCommand { + + /** @var Session */ + private $userSession; + /** @var IConfig */ + private $config; + + public function __construct(Session $userSession, IConfig $config) { + $this->userSession = $userSession; + $this->config = $config; + } + + public function process(LoginData $loginData): LoginResult { + if ($loginData->isRememberLogin() && $this->config->getSystemValue('auto_logout', false) === false) { + $this->userSession->createRememberMeToken($loginData->getUser()); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoggedInCheckCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoggedInCheckCommand.php new file mode 100644 index 0000000..d2546a6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoggedInCheckCommand.php @@ -0,0 +1,63 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\Authentication\Events\LoginFailed; +use OC\Core\Controller\LoginController; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\ILogger; +use OCP\IUserManager; + +class LoggedInCheckCommand extends ALoginCommand { + + /** @var ILogger */ + private $logger; + /** @var IEventDispatcher */ + private $dispatcher; + /** @var IUserManager */ + private $userManager; + + public function __construct(ILogger $logger, IEventDispatcher $dispatcher) { + $this->logger = $logger; + $this->dispatcher = $dispatcher; + } + + public function process(LoginData $loginData): LoginResult { + if ($loginData->getUser() === false) { + $loginName = $loginData->getUsername(); + $ip = $loginData->getRequest()->getRemoteAddress(); + + $this->logger->warning("Login failed: $loginName (Remote IP: $ip)"); + + $this->dispatcher->dispatchTyped(new LoginFailed($loginName)); + + return LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginData.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginData.php new file mode 100644 index 0000000..1da09f1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginData.php @@ -0,0 +1,121 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OCP\IRequest; +use OCP\IUser; + +class LoginData { + + /** @var IRequest */ + private $request; + + /** @var string */ + private $username; + + /** @var string */ + private $password; + + /** @var string */ + private $redirectUrl; + + /** @var string */ + private $timeZone; + + /** @var string */ + private $timeZoneOffset; + + /** @var IUser|false|null */ + private $user = null; + + /** @var bool */ + private $rememberLogin = true; + + public function __construct(IRequest $request, + string $username, + ?string $password, + string $redirectUrl = null, + string $timeZone = '', + string $timeZoneOffset = '') { + $this->request = $request; + $this->username = $username; + $this->password = $password; + $this->redirectUrl = $redirectUrl; + $this->timeZone = $timeZone; + $this->timeZoneOffset = $timeZoneOffset; + } + + public function getRequest(): IRequest { + return $this->request; + } + + public function setUsername(string $username): void { + $this->username = $username; + } + + public function getUsername(): string { + return $this->username; + } + + public function getPassword(): ?string { + return $this->password; + } + + public function getRedirectUrl(): ?string { + return $this->redirectUrl; + } + + public function getTimeZone(): string { + return $this->timeZone; + } + + public function getTimeZoneOffset(): string { + return $this->timeZoneOffset; + } + + /** + * @param IUser|false|null $user + */ + public function setUser($user) { + $this->user = $user; + } + + /** + * @return false|IUser|null + */ + public function getUser() { + return $this->user; + } + + public function setRememberLogin(bool $rememberLogin): void { + $this->rememberLogin = $rememberLogin; + } + + public function isRememberLogin(): bool { + return $this->rememberLogin; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginResult.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginResult.php new file mode 100644 index 0000000..6022830 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/LoginResult.php @@ -0,0 +1,82 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +class LoginResult { + + /** @var bool */ + private $success; + + /** @var LoginData */ + private $loginData; + + /** @var string|null */ + private $redirectUrl; + + /** @var string|null */ + private $errorMessage; + + private function __construct(bool $success, LoginData $loginData) { + $this->success = $success; + $this->loginData = $loginData; + } + + private function setRedirectUrl(string $url) { + $this->redirectUrl = $url; + } + + private function setErrorMessage(string $msg) { + $this->errorMessage = $msg; + } + + public static function success(LoginData $data, ?string $redirectUrl = null) { + $result = new static(true, $data); + if ($redirectUrl !== null) { + $result->setRedirectUrl($redirectUrl); + } + return $result; + } + + public static function failure(LoginData $data, string $msg = null): LoginResult { + $result = new static(false, $data); + if ($msg !== null) { + $result->setErrorMessage($msg); + } + return $result; + } + + public function isSuccess(): bool { + return $this->success; + } + + public function getRedirectUrl(): ?string { + return $this->redirectUrl; + } + + public function getErrorMessage(): ?string { + return $this->errorMessage; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/PreLoginHookCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/PreLoginHookCommand.php new file mode 100644 index 0000000..cb0b3ed --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/PreLoginHookCommand.php @@ -0,0 +1,55 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\Hooks\PublicEmitter; +use OCP\IUserManager; + +class PreLoginHookCommand extends ALoginCommand { + + /** @var IUserManager */ + private $userManager; + + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + } + + public function process(LoginData $loginData): LoginResult { + if ($this->userManager instanceof PublicEmitter) { + $this->userManager->emit( + '\OC\User', + 'preLogin', + [ + $loginData->getUsername(), + $loginData->getPassword(), + ] + ); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/SetUserTimezoneCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/SetUserTimezoneCommand.php new file mode 100644 index 0000000..0029f2f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/SetUserTimezoneCommand.php @@ -0,0 +1,61 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OCP\IConfig; +use OCP\ISession; + +class SetUserTimezoneCommand extends ALoginCommand { + + /** @var IConfig */ + private $config; + + /** @var ISession */ + private $session; + + public function __construct(IConfig $config, + ISession $session) { + $this->config = $config; + $this->session = $session; + } + + public function process(LoginData $loginData): LoginResult { + if ($loginData->getTimeZoneOffset() !== '') { + $this->config->setUserValue( + $loginData->getUser()->getUID(), + 'core', + 'timezone', + $loginData->getTimeZone() + ); + $this->session->set( + 'timezone', + $loginData->getTimeZoneOffset() + ); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/TwoFactorCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/TwoFactorCommand.php new file mode 100644 index 0000000..86b85c8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/TwoFactorCommand.php @@ -0,0 +1,94 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use function array_pop; +use function count; +use OC\Authentication\TwoFactorAuth\Manager; +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\IURLGenerator; + +class TwoFactorCommand extends ALoginCommand { + + /** @var Manager */ + private $twoFactorManager; + + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(Manager $twoFactorManager, + MandatoryTwoFactor $mandatoryTwoFactor, + IURLGenerator $urlGenerator) { + $this->twoFactorManager = $twoFactorManager; + $this->mandatoryTwoFactor = $mandatoryTwoFactor; + $this->urlGenerator = $urlGenerator; + } + + public function process(LoginData $loginData): LoginResult { + if (!$this->twoFactorManager->isTwoFactorAuthenticated($loginData->getUser())) { + return $this->processNextOrFinishSuccessfully($loginData); + } + + $this->twoFactorManager->prepareTwoFactorLogin($loginData->getUser(), $loginData->isRememberLogin()); + + $providerSet = $this->twoFactorManager->getProviderSet($loginData->getUser()); + $loginProviders = $this->twoFactorManager->getLoginSetupProviders($loginData->getUser()); + $providers = $providerSet->getPrimaryProviders(); + if (empty($providers) + && !$providerSet->isProviderMissing() + && !empty($loginProviders) + && $this->mandatoryTwoFactor->isEnforcedFor($loginData->getUser())) { + // No providers set up, but 2FA is enforced and setup providers are available + $url = 'core.TwoFactorChallenge.setupProviders'; + $urlParams = []; + } elseif (!$providerSet->isProviderMissing() && count($providers) === 1) { + // Single provider (and no missing ones), hence we can redirect to that provider's challenge page directly + /* @var $provider IProvider */ + $provider = array_pop($providers); + $url = 'core.TwoFactorChallenge.showChallenge'; + $urlParams = [ + 'challengeProviderId' => $provider->getId(), + ]; + } else { + $url = 'core.TwoFactorChallenge.selectChallenge'; + $urlParams = []; + } + + if ($loginData->getRedirectUrl() !== null) { + $urlParams['redirect_url'] = $loginData->getRedirectUrl(); + } + + return LoginResult::success( + $loginData, + $this->urlGenerator->linkToRoute($url, $urlParams) + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UidLoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UidLoginCommand.php new file mode 100644 index 0000000..8c9a516 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UidLoginCommand.php @@ -0,0 +1,56 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\User\Manager; +use OCP\IUser; + +class UidLoginCommand extends ALoginCommand { + + /** @var Manager */ + private $userManager; + + public function __construct(Manager $userManager) { + $this->userManager = $userManager; + } + + /** + * @param LoginData $loginData + * + * @return LoginResult + */ + public function process(LoginData $loginData): LoginResult { + /* @var $loginResult IUser */ + $user = $this->userManager->checkPasswordNoLogging( + $loginData->getUsername(), + $loginData->getPassword() + ); + + $loginData->setUser($user); + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php new file mode 100644 index 0000000..efaf555 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php @@ -0,0 +1,47 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OCP\ISession; + +class UpdateLastPasswordConfirmCommand extends ALoginCommand { + + /** @var ISession */ + private $session; + + public function __construct(ISession $session) { + $this->session = $session; + } + + public function process(LoginData $loginData): LoginResult { + $this->session->set( + 'last-password-confirm', + $loginData->getUser()->getLastLogin() + ); + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UserDisabledCheckCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UserDisabledCheckCommand.php new file mode 100644 index 0000000..e33853e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/UserDisabledCheckCommand.php @@ -0,0 +1,59 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OC\Authentication\Login; + +use OC\Core\Controller\LoginController; +use OCP\ILogger; +use OCP\IUserManager; + +class UserDisabledCheckCommand extends ALoginCommand { + + /** @var IUserManager */ + private $userManager; + + /** @var ILogger */ + private $logger; + + public function __construct(IUserManager $userManager, + ILogger $logger) { + $this->userManager = $userManager; + $this->logger = $logger; + } + + public function process(LoginData $loginData): LoginResult { + $user = $this->userManager->get($loginData->getUsername()); + if ($user !== null && $user->isEnabled() === false) { + $username = $loginData->getUsername(); + $ip = $loginData->getRequest()->getRemoteAddress(); + + $this->logger->warning("Login failed: $username disabled (Remote IP: $ip)"); + + return LoginResult::failure($loginData, LoginController::LOGIN_MSG_USERDISABLED); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnChain.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnChain.php new file mode 100644 index 0000000..1f737a1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnChain.php @@ -0,0 +1,98 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Login; + +class WebAuthnChain { + /** @var UserDisabledCheckCommand */ + private $userDisabledCheckCommand; + + /** @var LoggedInCheckCommand */ + private $loggedInCheckCommand; + + /** @var CompleteLoginCommand */ + private $completeLoginCommand; + + /** @var CreateSessionTokenCommand */ + private $createSessionTokenCommand; + + /** @var ClearLostPasswordTokensCommand */ + private $clearLostPasswordTokensCommand; + + /** @var UpdateLastPasswordConfirmCommand */ + private $updateLastPasswordConfirmCommand; + + /** @var SetUserTimezoneCommand */ + private $setUserTimezoneCommand; + + /** @var TwoFactorCommand */ + private $twoFactorCommand; + + /** @var FinishRememberedLoginCommand */ + private $finishRememberedLoginCommand; + + /** @var WebAuthnLoginCommand */ + private $webAuthnLoginCommand; + + public function __construct(UserDisabledCheckCommand $userDisabledCheckCommand, + WebAuthnLoginCommand $webAuthnLoginCommand, + LoggedInCheckCommand $loggedInCheckCommand, + CompleteLoginCommand $completeLoginCommand, + CreateSessionTokenCommand $createSessionTokenCommand, + ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, + UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, + SetUserTimezoneCommand $setUserTimezoneCommand, + TwoFactorCommand $twoFactorCommand, + FinishRememberedLoginCommand $finishRememberedLoginCommand + ) { + $this->userDisabledCheckCommand = $userDisabledCheckCommand; + $this->webAuthnLoginCommand = $webAuthnLoginCommand; + $this->loggedInCheckCommand = $loggedInCheckCommand; + $this->completeLoginCommand = $completeLoginCommand; + $this->createSessionTokenCommand = $createSessionTokenCommand; + $this->clearLostPasswordTokensCommand = $clearLostPasswordTokensCommand; + $this->updateLastPasswordConfirmCommand = $updateLastPasswordConfirmCommand; + $this->setUserTimezoneCommand = $setUserTimezoneCommand; + $this->twoFactorCommand = $twoFactorCommand; + $this->finishRememberedLoginCommand = $finishRememberedLoginCommand; + } + + public function process(LoginData $loginData): LoginResult { + $chain = $this->userDisabledCheckCommand; + $chain + ->setNext($this->webAuthnLoginCommand) + ->setNext($this->loggedInCheckCommand) + ->setNext($this->completeLoginCommand) + ->setNext($this->createSessionTokenCommand) + ->setNext($this->clearLostPasswordTokensCommand) + ->setNext($this->updateLastPasswordConfirmCommand) + ->setNext($this->setUserTimezoneCommand) + ->setNext($this->twoFactorCommand) + ->setNext($this->finishRememberedLoginCommand); + + return $chain->process($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnLoginCommand.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnLoginCommand.php new file mode 100644 index 0000000..c08108f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Login/WebAuthnLoginCommand.php @@ -0,0 +1,49 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Login; + +use OCP\IUserManager; + +class WebAuthnLoginCommand extends ALoginCommand { + + /** @var IUserManager */ + private $userManager; + + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + } + + public function process(LoginData $loginData): LoginResult { + $user = $this->userManager->get($loginData->getUsername()); + $loginData->setUser($user); + if ($user === null) { + $loginData->setUser(false); + } + + return $this->processNextOrFinishSuccessfully($loginData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Credentials.php b/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Credentials.php new file mode 100644 index 0000000..e4cbdcc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Credentials.php @@ -0,0 +1,70 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\LoginCredentials; + +use OCP\Authentication\LoginCredentials\ICredentials; + +class Credentials implements ICredentials { + + /** @var string */ + private $uid; + + /** @var string */ + private $loginName; + + /** @var string */ + private $password; + + /** + * @param string $uid + * @param string $loginName + * @param string $password + */ + public function __construct($uid, $loginName, $password) { + $this->uid = $uid; + $this->loginName = $loginName; + $this->password = $password; + } + + /** + * @return string + */ + public function getUID() { + return $this->uid; + } + + /** + * @return string + */ + public function getLoginName() { + return $this->loginName; + } + + /** + * @return string + */ + public function getPassword() { + return $this->password; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Store.php b/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Store.php new file mode 100644 index 0000000..29bc4f6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/LoginCredentials/Store.php @@ -0,0 +1,127 @@ + + * + * @author Christoph Wurst + * @author Lionel Elie Mamane + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\LoginCredentials; + +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Token\IProvider; +use OCP\Authentication\Exceptions\CredentialsUnavailableException; +use OCP\Authentication\LoginCredentials\ICredentials; +use OCP\Authentication\LoginCredentials\IStore; +use OCP\ILogger; +use OCP\ISession; +use OCP\Session\Exceptions\SessionNotAvailableException; +use OCP\Util; + +class Store implements IStore { + + /** @var ISession */ + private $session; + + /** @var ILogger */ + private $logger; + + /** @var IProvider|null */ + private $tokenProvider; + + /** + * @param ISession $session + * @param ILogger $logger + * @param IProvider $tokenProvider + */ + public function __construct(ISession $session, ILogger $logger, IProvider $tokenProvider = null) { + $this->session = $session; + $this->logger = $logger; + $this->tokenProvider = $tokenProvider; + + Util::connectHook('OC_User', 'post_login', $this, 'authenticate'); + } + + /** + * Hook listener on post login + * + * @param array $params + */ + public function authenticate(array $params) { + $this->session->set('login_credentials', json_encode($params)); + } + + /** + * Replace the session implementation + * + * @param ISession $session + */ + public function setSession(ISession $session) { + $this->session = $session; + } + + /** + * @since 12 + * + * @return ICredentials the login credentials of the current user + * @throws CredentialsUnavailableException + */ + public function getLoginCredentials(): ICredentials { + if ($this->tokenProvider === null) { + throw new CredentialsUnavailableException(); + } + + $trySession = false; + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + + $uid = $token->getUID(); + $user = $token->getLoginName(); + $password = $this->tokenProvider->getPassword($token, $sessionId); + + return new Credentials($uid, $user, $password); + } catch (SessionNotAvailableException $ex) { + $this->logger->debug('could not get login credentials because session is unavailable', ['app' => 'core']); + } catch (InvalidTokenException $ex) { + $this->logger->debug('could not get login credentials because the token is invalid', ['app' => 'core']); + $trySession = true; + } catch (PasswordlessTokenException $ex) { + $this->logger->debug('could not get login credentials because the token has no password', ['app' => 'core']); + $trySession = true; + } + + if ($trySession && $this->session->exists('login_credentials')) { + /** @var array $creds */ + $creds = json_decode($this->session->get('login_credentials'), true); + return new Credentials( + $creds['uid'], + $creds['loginName'] ?? $this->session->get('loginname') ?? $creds['uid'], // Pre 20 didn't have a loginName property, hence fall back to the session value and then to the UID + $creds['password'] + ); + } + + // If we reach this line, an exception was thrown. + throw new CredentialsUnavailableException(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Notifications/Notifier.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Notifications/Notifier.php new file mode 100644 index 0000000..fce8eb8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Notifications/Notifier.php @@ -0,0 +1,98 @@ + + * + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Notifications; + +use InvalidArgumentException; +use OCP\L10N\IFactory as IL10nFactory; +use OCP\Notification\INotification; +use OCP\Notification\INotifier; + +class Notifier implements INotifier { + + /** @var IL10nFactory */ + private $factory; + + public function __construct(IL10nFactory $l10nFactory) { + $this->factory = $l10nFactory; + } + + /** + * @inheritDoc + */ + public function prepare(INotification $notification, string $languageCode): INotification { + if ($notification->getApp() !== 'auth') { + // Not my app => throw + throw new InvalidArgumentException(); + } + + // Read the language from the notification + $l = $this->factory->get('lib', $languageCode); + + switch ($notification->getSubject()) { + case 'remote_wipe_start': + $notification->setParsedSubject( + $l->t('Remote wipe started') + )->setParsedMessage( + $l->t('A remote wipe was started on device %s', $notification->getSubjectParameters()) + ); + + return $notification; + case 'remote_wipe_finish': + $notification->setParsedSubject( + $l->t('Remote wipe finished') + )->setParsedMessage( + $l->t('The remote wipe on %s has finished', $notification->getSubjectParameters()) + ); + + return $notification; + default: + // Unknown subject => Unknown notification => throw + throw new InvalidArgumentException(); + } + } + + /** + * Identifier of the notifier, only use [a-z0-9_] + * + * @return string + * @since 17.0.0 + */ + public function getID(): string { + return 'auth'; + } + + /** + * Human readable name describing the notifier + * + * @return string + * @since 17.0.0 + */ + public function getName(): string { + return $this->factory->get('lib')->t('Authentication'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultToken.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultToken.php new file mode 100644 index 0000000..9df907d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultToken.php @@ -0,0 +1,210 @@ + + * @author Daniel Kesselberg + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use OCP\AppFramework\Db\Entity; + +/** + * @method void setId(int $id) + * @method void setUid(string $uid); + * @method void setLoginName(string $loginname) + * @method string getToken() + * @method void setType(int $type) + * @method int getType() + * @method void setRemember(int $remember) + * @method void setLastActivity(int $lastactivity) + * @method int getLastActivity() + * @method void setVersion(int $version) + */ +class DefaultToken extends Entity implements INamedToken { + public const VERSION = 1; + + /** @var string user UID */ + protected $uid; + + /** @var string login name used for generating the token */ + protected $loginName; + + /** @var string encrypted user password */ + protected $password; + + /** @var string token name (e.g. browser/OS) */ + protected $name; + + /** @var string */ + protected $token; + + /** @var int */ + protected $type; + + /** @var int */ + protected $remember; + + /** @var int */ + protected $lastActivity; + + /** @var int */ + protected $lastCheck; + + /** @var string */ + protected $scope; + + /** @var int */ + protected $expires; + + /** @var int */ + protected $version; + + public function __construct() { + $this->addType('uid', 'string'); + $this->addType('loginName', 'string'); + $this->addType('password', 'string'); + $this->addType('name', 'string'); + $this->addType('token', 'string'); + $this->addType('type', 'int'); + $this->addType('remember', 'int'); + $this->addType('lastActivity', 'int'); + $this->addType('lastCheck', 'int'); + $this->addType('scope', 'string'); + $this->addType('expires', 'int'); + $this->addType('version', 'int'); + } + + public function getId(): int { + return $this->id; + } + + public function getUID(): string { + return $this->uid; + } + + /** + * Get the login name used when generating the token + * + * @return string + */ + public function getLoginName(): string { + return parent::getLoginName(); + } + + /** + * Get the (encrypted) login password + * + * @return string|null + */ + public function getPassword() { + return parent::getPassword(); + } + + public function jsonSerialize() { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'lastActivity' => $this->lastActivity, + 'type' => $this->type, + 'scope' => $this->getScopeAsArray() + ]; + } + + /** + * Get the timestamp of the last password check + * + * @return int + */ + public function getLastCheck(): int { + return parent::getLastCheck(); + } + + /** + * Get the timestamp of the last password check + * + * @param int $time + */ + public function setLastCheck(int $time) { + parent::setLastCheck($time); + } + + public function getScope(): string { + $scope = parent::getScope(); + if ($scope === null) { + return ''; + } + + return $scope; + } + + public function getScopeAsArray(): array { + $scope = json_decode($this->getScope(), true); + if (!$scope) { + return [ + 'filesystem'=> true + ]; + } + return $scope; + } + + public function setScope($scope) { + if (\is_array($scope)) { + parent::setScope(json_encode($scope)); + } else { + parent::setScope((string)$scope); + } + } + + public function getName(): string { + return parent::getName(); + } + + public function setName(string $name): void { + parent::setName($name); + } + + public function getRemember(): int { + return parent::getRemember(); + } + + public function setToken(string $token) { + parent::setToken($token); + } + + public function setPassword(string $password = null) { + parent::setPassword($password); + } + + public function setExpires($expires) { + parent::setExpires($expires); + } + + /** + * @return int|null + */ + public function getExpires() { + return parent::getExpires(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenCleanupJob.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenCleanupJob.php new file mode 100644 index 0000000..0686f40 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenCleanupJob.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use OC; +use OC\BackgroundJob\Job; + +class DefaultTokenCleanupJob extends Job { + protected function run($argument) { + /* @var $provider IProvider */ + $provider = OC::$server->query(IProvider::class); + $provider->invalidateOldTokens(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenMapper.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenMapper.php new file mode 100644 index 0000000..e51033e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -0,0 +1,170 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author Marcel Waldvogel + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class DefaultTokenMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'authtoken'); + } + + /** + * Invalidate (delete) a given token + * + * @param string $token + */ + public function invalidate(string $token) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + } + + /** + * @param int $olderThan + * @param int $remember + */ + public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + } + + /** + * Get the user UID for the given token + * + * @param string $token + * @throws DoesNotExistException + * @return DefaultToken + */ + public function getToken(string $token): DefaultToken { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + } + return DefaultToken::fromRow($data); + } + + /** + * Get the token for $id + * + * @param int $id + * @throws DoesNotExistException + * @return DefaultToken + */ + public function getTokenById(int $id): DefaultToken { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') + ->from('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + } + return DefaultToken::fromRow($data); + } + + /** + * Get all tokens of a user + * + * The provider may limit the number of result rows in case of an abuse + * where a high number of (session) tokens is generated + * + * @param string $uid + * @return DefaultToken[] + */ + public function getTokenByUser(string $uid): array { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') + ->from('authtoken') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))) + ->setMaxResults(1000); + $result = $qb->execute(); + $data = $result->fetchAll(); + $result->closeCursor(); + + $entities = array_map(function ($row) { + return DefaultToken::fromRow($row); + }, $data); + + return $entities; + } + + public function deleteById(string $uid, int $id) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))); + $qb->execute(); + } + + /** + * delete all auth token which belong to a specific client if the client was deleted + * + * @param string $name + */ + public function deleteByName(string $name) { + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR)) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(DefaultToken::VERSION, IQueryBuilder::PARAM_INT))); + $qb->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenProvider.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenProvider.php new file mode 100644 index 0000000..ee8a28d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -0,0 +1,353 @@ + + * + * @author Christoph Wurst + * @author Flávio Gomes da Silva Lisboa + * @author Joas Schilling + * @author Lukas Reschke + * @author Martin + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use Exception; +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Security\ICrypto; + +class DefaultTokenProvider implements IProvider { + + /** @var DefaultTokenMapper */ + private $mapper; + + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + + /** @var ILogger */ + private $logger; + + /** @var ITimeFactory */ + private $time; + + public function __construct(DefaultTokenMapper $mapper, + ICrypto $crypto, + IConfig $config, + ILogger $logger, + ITimeFactory $time) { + $this->mapper = $mapper; + $this->crypto = $crypto; + $this->config = $config; + $this->logger = $logger; + $this->time = $time; + } + + /** + * Create and persist a new token + * + * @param string $token + * @param string $uid + * @param string $loginName + * @param string|null $password + * @param string $name + * @param int $type token type + * @param int $remember whether the session token should be used for remember-me + * @return IToken + */ + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { + $dbToken = new DefaultToken(); + $dbToken->setUid($uid); + $dbToken->setLoginName($loginName); + if (!is_null($password)) { + $dbToken->setPassword($this->encryptPassword($password, $token)); + } + $dbToken->setName($name); + $dbToken->setToken($this->hashToken($token)); + $dbToken->setType($type); + $dbToken->setRemember($remember); + $dbToken->setLastActivity($this->time->getTime()); + $dbToken->setLastCheck($this->time->getTime()); + $dbToken->setVersion(DefaultToken::VERSION); + + $this->mapper->insert($dbToken); + + return $dbToken; + } + + /** + * Save the updated token + * + * @param IToken $token + * @throws InvalidTokenException + */ + public function updateToken(IToken $token) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException("Invalid token type"); + } + $this->mapper->update($token); + } + + /** + * Update token activity timestamp + * + * @throws InvalidTokenException + * @param IToken $token + */ + public function updateTokenActivity(IToken $token) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException("Invalid token type"); + } + /** @var DefaultToken $token */ + $now = $this->time->getTime(); + if ($token->getLastActivity() < ($now - 60)) { + // Update token only once per minute + $token->setLastActivity($now); + $this->mapper->update($token); + } + } + + public function getTokenByUser(string $uid): array { + return $this->mapper->getTokenByUser($uid); + } + + /** + * Get a token by token + * + * @param string $tokenId + * @throws InvalidTokenException + * @throws ExpiredTokenException + * @return IToken + */ + public function getToken(string $tokenId): IToken { + try { + $token = $this->mapper->getToken($this->hashToken($tokenId)); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException("Token does not exist", 0, $ex); + } + + if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + return $token; + } + + /** + * Get a token by token id + * + * @param int $tokenId + * @throws InvalidTokenException + * @throws ExpiredTokenException + * @return IToken + */ + public function getTokenById(int $tokenId): IToken { + try { + $token = $this->mapper->getTokenById($tokenId); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException("Token with ID $tokenId does not exist", 0, $ex); + } + + if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + return $token; + } + + /** + * @param string $oldSessionId + * @param string $sessionId + * @throws InvalidTokenException + * @return IToken + */ + public function renewSessionToken(string $oldSessionId, string $sessionId): IToken { + $token = $this->getToken($oldSessionId); + + $newToken = new DefaultToken(); + $newToken->setUid($token->getUID()); + $newToken->setLoginName($token->getLoginName()); + if (!is_null($token->getPassword())) { + $password = $this->decryptPassword($token->getPassword(), $oldSessionId); + $newToken->setPassword($this->encryptPassword($password, $sessionId)); + } + $newToken->setName($token->getName()); + $newToken->setToken($this->hashToken($sessionId)); + $newToken->setType(IToken::TEMPORARY_TOKEN); + $newToken->setRemember($token->getRemember()); + $newToken->setLastActivity($this->time->getTime()); + $this->mapper->insert($newToken); + $this->mapper->delete($token); + + return $newToken; + } + + /** + * @param IToken $savedToken + * @param string $tokenId session token + * @throws InvalidTokenException + * @throws PasswordlessTokenException + * @return string + */ + public function getPassword(IToken $savedToken, string $tokenId): string { + $password = $savedToken->getPassword(); + if (is_null($password)) { + throw new PasswordlessTokenException(); + } + return $this->decryptPassword($password, $tokenId); + } + + /** + * Encrypt and set the password of the given token + * + * @param IToken $token + * @param string $tokenId + * @param string $password + * @throws InvalidTokenException + */ + public function setPassword(IToken $token, string $tokenId, string $password) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException("Invalid token type"); + } + /** @var DefaultToken $token */ + $token->setPassword($this->encryptPassword($password, $tokenId)); + $this->mapper->update($token); + } + + /** + * Invalidate (delete) the given session token + * + * @param string $token + */ + public function invalidateToken(string $token) { + $this->mapper->invalidate($this->hashToken($token)); + } + + public function invalidateTokenById(string $uid, int $id) { + $this->mapper->deleteById($uid, $id); + } + + /** + * Invalidate (delete) old session tokens + */ + public function invalidateOldTokens() { + $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24); + $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']); + $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER); + $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']); + $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER); + } + + /** + * Rotate the token. Usefull for for example oauth tokens + * + * @param IToken $token + * @param string $oldTokenId + * @param string $newTokenId + * @return IToken + */ + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { + try { + $password = $this->getPassword($token, $oldTokenId); + $token->setPassword($this->encryptPassword($password, $newTokenId)); + } catch (PasswordlessTokenException $e) { + } + + $token->setToken($this->hashToken($newTokenId)); + $this->updateToken($token); + + return $token; + } + + /** + * @param string $token + * @return string + */ + private function hashToken(string $token): string { + $secret = $this->config->getSystemValue('secret'); + return hash('sha512', $token . $secret); + } + + /** + * Encrypt the given password + * + * The token is used as key + * + * @param string $password + * @param string $token + * @return string encrypted password + */ + private function encryptPassword(string $password, string $token): string { + $secret = $this->config->getSystemValue('secret'); + return $this->crypto->encrypt($password, $token . $secret); + } + + /** + * Decrypt the given password + * + * The token is used as key + * + * @param string $password + * @param string $token + * @throws InvalidTokenException + * @return string the decrypted key + */ + private function decryptPassword(string $password, string $token): string { + $secret = $this->config->getSystemValue('secret'); + try { + return $this->crypto->decrypt($password, $token . $secret); + } catch (Exception $ex) { + // Delete the invalid token + $this->invalidateToken($token); + throw new InvalidTokenException("Can not decrypt token password: " . $ex->getMessage(), 0, $ex); + } + } + + public function markPasswordInvalid(IToken $token, string $tokenId) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + //No need to mark as invalid. We just invalide default tokens + $this->invalidateToken($tokenId); + } + + public function updatePasswords(string $uid, string $password) { + // Nothing to do here + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/INamedToken.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/INamedToken.php new file mode 100644 index 0000000..3075587 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/INamedToken.php @@ -0,0 +1,38 @@ + + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +interface INamedToken extends IToken { + /** + * Set token name + * + * @param string $name + * @return void + */ + public function setName(string $name): void; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IProvider.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IProvider.php new file mode 100644 index 0000000..2b6223f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IProvider.php @@ -0,0 +1,187 @@ + + * @author Lukas Reschke + * @author Marcel Waldvogel + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\WipeTokenException; + +interface IProvider { + + + /** + * Create and persist a new token + * + * @param string $token + * @param string $uid + * @param string $loginName + * @param string|null $password + * @param string $name + * @param int $type token type + * @param int $remember whether the session token should be used for remember-me + * @return IToken + * @throws \RuntimeException when OpenSSL reports a problem + */ + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken; + + /** + * Get a token by token id + * + * @param string $tokenId + * @throws InvalidTokenException + * @throws ExpiredTokenException + * @throws WipeTokenException + * @return IToken + */ + public function getToken(string $tokenId): IToken; + + /** + * Get a token by token id + * + * @param int $tokenId + * @throws InvalidTokenException + * @throws ExpiredTokenException + * @throws WipeTokenException + * @return IToken + */ + public function getTokenById(int $tokenId): IToken; + + /** + * Duplicate an existing session token + * + * @param string $oldSessionId + * @param string $sessionId + * @throws InvalidTokenException + * @throws \RuntimeException when OpenSSL reports a problem + * @return IToken The new token + */ + public function renewSessionToken(string $oldSessionId, string $sessionId): IToken; + + /** + * Invalidate (delete) the given session token + * + * @param string $token + */ + public function invalidateToken(string $token); + + /** + * Invalidate (delete) the given token + * + * @param string $uid + * @param int $id + */ + public function invalidateTokenById(string $uid, int $id); + + /** + * Invalidate (delete) old session tokens + */ + public function invalidateOldTokens(); + + /** + * Save the updated token + * + * @param IToken $token + */ + public function updateToken(IToken $token); + + /** + * Update token activity timestamp + * + * @param IToken $token + */ + public function updateTokenActivity(IToken $token); + + /** + * Get all tokens of a user + * + * The provider may limit the number of result rows in case of an abuse + * where a high number of (session) tokens is generated + * + * @param string $uid + * @return IToken[] + */ + public function getTokenByUser(string $uid): array; + + /** + * Get the (unencrypted) password of the given token + * + * @param IToken $savedToken + * @param string $tokenId + * @throws InvalidTokenException + * @throws PasswordlessTokenException + * @return string + */ + public function getPassword(IToken $savedToken, string $tokenId): string; + + /** + * Encrypt and set the password of the given token + * + * @param IToken $token + * @param string $tokenId + * @param string $password + * @throws InvalidTokenException + */ + public function setPassword(IToken $token, string $tokenId, string $password); + + /** + * Rotate the token. Usefull for for example oauth tokens + * + * @param IToken $token + * @param string $oldTokenId + * @param string $newTokenId + * @return IToken + * @throws \RuntimeException when OpenSSL reports a problem + */ + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken; + + /** + * Marks a token as having an invalid password. + * + * @param IToken $token + * @param string $tokenId + */ + public function markPasswordInvalid(IToken $token, string $tokenId); + + /** + * Update all the passwords of $uid if required + * + * @param string $uid + * @param string $password + */ + public function updatePasswords(string $uid, string $password); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IToken.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IToken.php new file mode 100644 index 0000000..326e955 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IToken.php @@ -0,0 +1,135 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\Token; + +use JsonSerializable; + +interface IToken extends JsonSerializable { + public const TEMPORARY_TOKEN = 0; + public const PERMANENT_TOKEN = 1; + public const WIPE_TOKEN = 2; + public const DO_NOT_REMEMBER = 0; + public const REMEMBER = 1; + + /** + * Get the token ID + * + * @return int + */ + public function getId(): int; + + /** + * Get the user UID + * + * @return string + */ + public function getUID(): string; + + /** + * Get the login name used when generating the token + * + * @return string + */ + public function getLoginName(): string; + + /** + * Get the (encrypted) login password + * + * @return string|null + */ + public function getPassword(); + + /** + * Get the timestamp of the last password check + * + * @return int + */ + public function getLastCheck(): int; + + /** + * Set the timestamp of the last password check + * + * @param int $time + */ + public function setLastCheck(int $time); + + /** + * Get the authentication scope for this token + * + * @return string + */ + public function getScope(): string; + + /** + * Get the authentication scope for this token + * + * @return array + */ + public function getScopeAsArray(): array; + + /** + * Set the authentication scope for this token + * + * @param array $scope + */ + public function setScope($scope); + + /** + * Get the name of the token + * @return string + */ + public function getName(): string; + + /** + * Get the remember state of the token + * + * @return int + */ + public function getRemember(): int; + + /** + * Set the token + * + * @param string $token + */ + public function setToken(string $token); + + /** + * Set the password + * + * @param string $password + */ + public function setPassword(string $password); + + /** + * Set the expiration time of the token + * + * @param int|null $expires + */ + public function setExpires($expires); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IWipeableToken.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IWipeableToken.php new file mode 100644 index 0000000..efc542f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/IWipeableToken.php @@ -0,0 +1,36 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +interface IWipeableToken extends IToken { + + /** + * Mark the token for remote wipe + */ + public function wipe(): void; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/Manager.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/Manager.php new file mode 100644 index 0000000..073569d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/Manager.php @@ -0,0 +1,275 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\WipeTokenException; + +class Manager implements IProvider { + + /** @var DefaultTokenProvider */ + private $defaultTokenProvider; + + /** @var PublicKeyTokenProvider */ + private $publicKeyTokenProvider; + + public function __construct(DefaultTokenProvider $defaultTokenProvider, PublicKeyTokenProvider $publicKeyTokenProvider) { + $this->defaultTokenProvider = $defaultTokenProvider; + $this->publicKeyTokenProvider = $publicKeyTokenProvider; + } + + /** + * Create and persist a new token + * + * @param string $token + * @param string $uid + * @param string $loginName + * @param string|null $password + * @param string $name + * @param int $type token type + * @param int $remember whether the session token should be used for remember-me + * @return IToken + */ + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { + try { + return $this->publicKeyTokenProvider->generateToken( + $token, + $uid, + $loginName, + $password, + $name, + $type, + $remember + ); + } catch (UniqueConstraintViolationException $e) { + // It's rare, but if two requests of the same session (e.g. env-based SAML) + // try to create the session token they might end up here at the same time + // because we use the session ID as token and the db token is created anew + // with every request. + // + // If the UIDs match, then this should be fine. + $existing = $this->getToken($token); + if ($existing->getUID() !== $uid) { + throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e); + } + return $existing; + } + } + + /** + * Save the updated token + * + * @param IToken $token + * @throws InvalidTokenException + */ + public function updateToken(IToken $token) { + $provider = $this->getProvider($token); + $provider->updateToken($token); + } + + /** + * Update token activity timestamp + * + * @throws InvalidTokenException + * @param IToken $token + */ + public function updateTokenActivity(IToken $token) { + $provider = $this->getProvider($token); + $provider->updateTokenActivity($token); + } + + /** + * @param string $uid + * @return IToken[] + */ + public function getTokenByUser(string $uid): array { + $old = $this->defaultTokenProvider->getTokenByUser($uid); + $new = $this->publicKeyTokenProvider->getTokenByUser($uid); + + return array_merge($old, $new); + } + + /** + * Get a token by token + * + * @param string $tokenId + * @throws InvalidTokenException + * @throws \RuntimeException when OpenSSL reports a problem + * @return IToken + */ + public function getToken(string $tokenId): IToken { + try { + return $this->publicKeyTokenProvider->getToken($tokenId); + } catch (WipeTokenException $e) { + throw $e; + } catch (ExpiredTokenException $e) { + throw $e; + } catch (InvalidTokenException $e) { + // No worries we try to convert it to a PublicKey Token + } + + //Convert! + $token = $this->defaultTokenProvider->getToken($tokenId); + + try { + $password = $this->defaultTokenProvider->getPassword($token, $tokenId); + } catch (PasswordlessTokenException $e) { + $password = null; + } + + return $this->publicKeyTokenProvider->convertToken($token, $tokenId, $password); + } + + /** + * Get a token by token id + * + * @param int $tokenId + * @throws InvalidTokenException + * @return IToken + */ + public function getTokenById(int $tokenId): IToken { + try { + return $this->publicKeyTokenProvider->getTokenById($tokenId); + } catch (ExpiredTokenException $e) { + throw $e; + } catch (WipeTokenException $e) { + throw $e; + } catch (InvalidTokenException $e) { + return $this->defaultTokenProvider->getTokenById($tokenId); + } + } + + /** + * @param string $oldSessionId + * @param string $sessionId + * @throws InvalidTokenException + * @return IToken + */ + public function renewSessionToken(string $oldSessionId, string $sessionId): IToken { + try { + return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId); + } catch (ExpiredTokenException $e) { + throw $e; + } catch (InvalidTokenException $e) { + return $this->defaultTokenProvider->renewSessionToken($oldSessionId, $sessionId); + } + } + + /** + * @param IToken $savedToken + * @param string $tokenId session token + * @throws InvalidTokenException + * @throws PasswordlessTokenException + * @return string + */ + public function getPassword(IToken $savedToken, string $tokenId): string { + $provider = $this->getProvider($savedToken); + return $provider->getPassword($savedToken, $tokenId); + } + + public function setPassword(IToken $token, string $tokenId, string $password) { + $provider = $this->getProvider($token); + $provider->setPassword($token, $tokenId, $password); + } + + public function invalidateToken(string $token) { + $this->defaultTokenProvider->invalidateToken($token); + $this->publicKeyTokenProvider->invalidateToken($token); + } + + public function invalidateTokenById(string $uid, int $id) { + $this->defaultTokenProvider->invalidateTokenById($uid, $id); + $this->publicKeyTokenProvider->invalidateTokenById($uid, $id); + } + + public function invalidateOldTokens() { + $this->defaultTokenProvider->invalidateOldTokens(); + $this->publicKeyTokenProvider->invalidateOldTokens(); + } + + /** + * @param IToken $token + * @param string $oldTokenId + * @param string $newTokenId + * @return IToken + * @throws InvalidTokenException + * @throws \RuntimeException when OpenSSL reports a problem + */ + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { + if ($token instanceof DefaultToken) { + try { + $password = $this->defaultTokenProvider->getPassword($token, $oldTokenId); + } catch (PasswordlessTokenException $e) { + $password = null; + } + + return $this->publicKeyTokenProvider->convertToken($token, $newTokenId, $password); + } + + if ($token instanceof PublicKeyToken) { + return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId); + } + + throw new InvalidTokenException(); + } + + /** + * @param IToken $token + * @return IProvider + * @throws InvalidTokenException + */ + private function getProvider(IToken $token): IProvider { + if ($token instanceof DefaultToken) { + return $this->defaultTokenProvider; + } + if ($token instanceof PublicKeyToken) { + return $this->publicKeyTokenProvider; + } + throw new InvalidTokenException(); + } + + + public function markPasswordInvalid(IToken $token, string $tokenId) { + $this->getProvider($token)->markPasswordInvalid($token, $tokenId); + } + + public function updatePasswords(string $uid, string $password) { + $this->defaultTokenProvider->updatePasswords($uid, $password); + $this->publicKeyTokenProvider->updatePasswords($uid, $password); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyToken.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyToken.php new file mode 100644 index 0000000..3203734 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyToken.php @@ -0,0 +1,235 @@ + + * @author Daniel Kesselberg + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +use OCP\AppFramework\Db\Entity; + +/** + * @method void setId(int $id) + * @method void setUid(string $uid); + * @method void setLoginName(string $loginname) + * @method string getToken() + * @method void setType(int $type) + * @method int getType() + * @method void setRemember(int $remember) + * @method void setLastActivity(int $lastactivity) + * @method int getLastActivity() + * @method string getPrivateKey() + * @method void setPrivateKey(string $key) + * @method string getPublicKey() + * @method void setPublicKey(string $key) + * @method void setVersion(int $version) + * @method bool getPasswordInvalid() + */ +class PublicKeyToken extends Entity implements INamedToken, IWipeableToken { + public const VERSION = 2; + + /** @var string user UID */ + protected $uid; + + /** @var string login name used for generating the token */ + protected $loginName; + + /** @var string encrypted user password */ + protected $password; + + /** @var string token name (e.g. browser/OS) */ + protected $name; + + /** @var string */ + protected $token; + + /** @var int */ + protected $type; + + /** @var int */ + protected $remember; + + /** @var int */ + protected $lastActivity; + + /** @var int */ + protected $lastCheck; + + /** @var string */ + protected $scope; + + /** @var int */ + protected $expires; + + /** @var string */ + protected $privateKey; + + /** @var string */ + protected $publicKey; + + /** @var int */ + protected $version; + + /** @var bool */ + protected $passwordInvalid; + + public function __construct() { + $this->addType('uid', 'string'); + $this->addType('loginName', 'string'); + $this->addType('password', 'string'); + $this->addType('name', 'string'); + $this->addType('token', 'string'); + $this->addType('type', 'int'); + $this->addType('remember', 'int'); + $this->addType('lastActivity', 'int'); + $this->addType('lastCheck', 'int'); + $this->addType('scope', 'string'); + $this->addType('expires', 'int'); + $this->addType('publicKey', 'string'); + $this->addType('privateKey', 'string'); + $this->addType('version', 'int'); + $this->addType('passwordInvalid', 'bool'); + } + + public function getId(): int { + return $this->id; + } + + public function getUID(): string { + return $this->uid; + } + + /** + * Get the login name used when generating the token + * + * @return string + */ + public function getLoginName(): string { + return parent::getLoginName(); + } + + /** + * Get the (encrypted) login password + * + * @return string|null + */ + public function getPassword() { + return parent::getPassword(); + } + + public function jsonSerialize() { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'lastActivity' => $this->lastActivity, + 'type' => $this->type, + 'scope' => $this->getScopeAsArray() + ]; + } + + /** + * Get the timestamp of the last password check + * + * @return int + */ + public function getLastCheck(): int { + return parent::getLastCheck(); + } + + /** + * Get the timestamp of the last password check + * + * @param int $time + */ + public function setLastCheck(int $time) { + parent::setLastCheck($time); + } + + public function getScope(): string { + $scope = parent::getScope(); + if ($scope === null) { + return ''; + } + + return $scope; + } + + public function getScopeAsArray(): array { + $scope = json_decode($this->getScope(), true); + if (!$scope) { + return [ + 'filesystem'=> true + ]; + } + return $scope; + } + + public function setScope($scope) { + if (is_array($scope)) { + parent::setScope(json_encode($scope)); + } else { + parent::setScope((string)$scope); + } + } + + public function getName(): string { + return parent::getName(); + } + + public function setName(string $name): void { + parent::setName($name); + } + + public function getRemember(): int { + return parent::getRemember(); + } + + public function setToken(string $token) { + parent::setToken($token); + } + + public function setPassword(string $password = null) { + parent::setPassword($password); + } + + public function setExpires($expires) { + parent::setExpires($expires); + } + + /** + * @return int|null + */ + public function getExpires() { + return parent::getExpires(); + } + + public function setPasswordInvalid(bool $invalid) { + parent::setPasswordInvalid($invalid); + } + + public function wipe(): void { + parent::setType(IToken::WIPE_TOKEN); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenMapper.php new file mode 100644 index 0000000..e05325f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -0,0 +1,190 @@ + + * + * @author Daniel Kesselberg + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class PublicKeyTokenMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'authtoken'); + } + + /** + * Invalidate (delete) a given token + * + * @param string $token + */ + public function invalidate(string $token) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + } + + /** + * @param int $olderThan + * @param int $remember + */ + public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + } + + /** + * Get the user UID for the given token + * + * @throws DoesNotExistException + */ + public function getToken(string $token): PublicKeyToken { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('*') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + } + return PublicKeyToken::fromRow($data); + } + + /** + * Get the token for $id + * + * @throws DoesNotExistException + */ + public function getTokenById(int $id): PublicKeyToken { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('*') + ->from('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + } + return PublicKeyToken::fromRow($data); + } + + /** + * Get all tokens of a user + * + * The provider may limit the number of result rows in case of an abuse + * where a high number of (session) tokens is generated + * + * @param string $uid + * @return PublicKeyToken[] + */ + public function getTokenByUser(string $uid): array { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('authtoken') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->setMaxResults(1000); + $result = $qb->execute(); + $data = $result->fetchAll(); + $result->closeCursor(); + + $entities = array_map(function ($row) { + return PublicKeyToken::fromRow($row); + }, $data); + + return $entities; + } + + public function deleteById(string $uid, int $id) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))); + $qb->execute(); + } + + /** + * delete all auth token which belong to a specific client if the client was deleted + * + * @param string $name + */ + public function deleteByName(string $name) { + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR)) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))); + $qb->execute(); + } + + public function deleteTempToken(PublicKeyToken $except) { + $qb = $this->db->getQueryBuilder(); + + $qb->delete('authtoken') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($except->getUID()))) + ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN))) + ->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($except->getId()))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))); + + $qb->execute(); + } + + public function hasExpiredTokens(string $uid): bool { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('authtoken') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('password_invalid', $qb->createNamedParameter(true), IQueryBuilder::PARAM_BOOL)) + ->setMaxResults(1); + + $cursor = $qb->execute(); + $data = $cursor->fetchAll(); + $cursor->closeCursor(); + + return count($data) === 1; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenProvider.php new file mode 100644 index 0000000..a6498ca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -0,0 +1,439 @@ + + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\TokenPasswordExpiredException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\WipeTokenException; +use OC\Cache\CappedMemoryCache; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Security\ICrypto; + +class PublicKeyTokenProvider implements IProvider { + /** @var PublicKeyTokenMapper */ + private $mapper; + + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + + /** @var ILogger $logger */ + private $logger; + + /** @var ITimeFactory $time */ + private $time; + + /** @var CappedMemoryCache */ + private $cache; + + public function __construct(PublicKeyTokenMapper $mapper, + ICrypto $crypto, + IConfig $config, + ILogger $logger, + ITimeFactory $time) { + $this->mapper = $mapper; + $this->crypto = $crypto; + $this->config = $config; + $this->logger = $logger; + $this->time = $time; + + $this->cache = new CappedMemoryCache(); + } + + /** + * {@inheritDoc} + */ + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { + $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember); + $this->mapper->insert($dbToken); + + // Add the token to the cache + $this->cache[$dbToken->getToken()] = $dbToken; + + return $dbToken; + } + + public function getToken(string $tokenId): IToken { + $tokenHash = $this->hashToken($tokenId); + + if (isset($this->cache[$tokenHash])) { + $token = $this->cache[$tokenHash]; + } else { + try { + $token = $this->mapper->getToken($this->hashToken($tokenId)); + $this->cache[$token->getToken()] = $token; + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); + } + } + + if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + if ($token->getType() === IToken::WIPE_TOKEN) { + throw new WipeTokenException($token); + } + + if ($token->getPasswordInvalid() === true) { + //The password is invalid we should throw an TokenPasswordExpiredException + throw new TokenPasswordExpiredException($token); + } + + return $token; + } + + public function getTokenById(int $tokenId): IToken { + try { + $token = $this->mapper->getTokenById($tokenId); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex); + } + + if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + if ($token->getType() === IToken::WIPE_TOKEN) { + throw new WipeTokenException($token); + } + + if ($token->getPasswordInvalid() === true) { + //The password is invalid we should throw an TokenPasswordExpiredException + throw new TokenPasswordExpiredException($token); + } + + return $token; + } + + public function renewSessionToken(string $oldSessionId, string $sessionId): IToken { + $this->cache->clear(); + + $token = $this->getToken($oldSessionId); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + $password = null; + if (!is_null($token->getPassword())) { + $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId); + $password = $this->decryptPassword($token->getPassword(), $privateKey); + } + + $newToken = $this->generateToken( + $sessionId, + $token->getUID(), + $token->getLoginName(), + $password, + $token->getName(), + IToken::TEMPORARY_TOKEN, + $token->getRemember() + ); + + $this->mapper->delete($token); + + return $newToken; + } + + public function invalidateToken(string $token) { + $this->cache->clear(); + + $this->mapper->invalidate($this->hashToken($token)); + } + + public function invalidateTokenById(string $uid, int $id) { + $this->cache->clear(); + + $this->mapper->deleteById($uid, $id); + } + + public function invalidateOldTokens() { + $this->cache->clear(); + + $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24); + $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']); + $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER); + $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']); + $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER); + } + + public function updateToken(IToken $token) { + $this->cache->clear(); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + $this->mapper->update($token); + } + + public function updateTokenActivity(IToken $token) { + $this->cache->clear(); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + $activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60); + $activityInterval = min(max($activityInterval, 0), 300); + + /** @var DefaultToken $token */ + $now = $this->time->getTime(); + if ($token->getLastActivity() < ($now - $activityInterval)) { + // Update token only once per minute + $token->setLastActivity($now); + $this->mapper->update($token); + } + } + + public function getTokenByUser(string $uid): array { + return $this->mapper->getTokenByUser($uid); + } + + public function getPassword(IToken $savedToken, string $tokenId): string { + if (!($savedToken instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + if ($savedToken->getPassword() === null) { + throw new PasswordlessTokenException(); + } + + // Decrypt private key with tokenId + $privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId); + + // Decrypt password with private key + return $this->decryptPassword($savedToken->getPassword(), $privateKey); + } + + public function setPassword(IToken $token, string $tokenId, string $password) { + $this->cache->clear(); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + // When changing passwords all temp tokens are deleted + $this->mapper->deleteTempToken($token); + + // Update the password for all tokens + $tokens = $this->mapper->getTokenByUser($token->getUID()); + foreach ($tokens as $t) { + $publicKey = $t->getPublicKey(); + $t->setPassword($this->encryptPassword($password, $publicKey)); + $this->updateToken($t); + } + } + + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { + $this->cache->clear(); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + // Decrypt private key with oldTokenId + $privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId); + // Encrypt with the new token + $token->setPrivateKey($this->encrypt($privateKey, $newTokenId)); + + $token->setToken($this->hashToken($newTokenId)); + $this->updateToken($token); + + return $token; + } + + private function encrypt(string $plaintext, string $token): string { + $secret = $this->config->getSystemValue('secret'); + return $this->crypto->encrypt($plaintext, $token . $secret); + } + + /** + * @throws InvalidTokenException + */ + private function decrypt(string $cipherText, string $token): string { + $secret = $this->config->getSystemValue('secret'); + try { + return $this->crypto->decrypt($cipherText, $token . $secret); + } catch (\Exception $ex) { + // Delete the invalid token + $this->invalidateToken($token); + throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex); + } + } + + private function encryptPassword(string $password, string $publicKey): string { + openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING); + $encryptedPassword = base64_encode($encryptedPassword); + + return $encryptedPassword; + } + + private function decryptPassword(string $encryptedPassword, string $privateKey): string { + $encryptedPassword = base64_decode($encryptedPassword); + openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING); + + return $password; + } + + private function hashToken(string $token): string { + $secret = $this->config->getSystemValue('secret'); + return hash('sha512', $token . $secret); + } + + /** + * Convert a DefaultToken to a publicKeyToken + * This will also be updated directly in the Database + * @throws \RuntimeException when OpenSSL reports a problem + */ + public function convertToken(DefaultToken $defaultToken, string $token, $password): PublicKeyToken { + $this->cache->clear(); + + $pkToken = $this->newToken( + $token, + $defaultToken->getUID(), + $defaultToken->getLoginName(), + $password, + $defaultToken->getName(), + $defaultToken->getType(), + $defaultToken->getRemember() + ); + + $pkToken->setExpires($defaultToken->getExpires()); + $pkToken->setId($defaultToken->getId()); + + return $this->mapper->update($pkToken); + } + + /** + * @throws \RuntimeException when OpenSSL reports a problem + */ + private function newToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type, + int $remember): PublicKeyToken { + $dbToken = new PublicKeyToken(); + $dbToken->setUid($uid); + $dbToken->setLoginName($loginName); + + $config = array_merge([ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + ], $this->config->getSystemValue('openssl', [])); + + // Generate new key + $res = openssl_pkey_new($config); + if ($res === false) { + $this->logOpensslError(); + throw new \RuntimeException('OpenSSL reported a problem'); + } + + if (openssl_pkey_export($res, $privateKey, null, $config) === false) { + $this->logOpensslError(); + throw new \RuntimeException('OpenSSL reported a problem'); + } + + // Extract the public key from $res to $pubKey + $publicKey = openssl_pkey_get_details($res); + $publicKey = $publicKey['key']; + + $dbToken->setPublicKey($publicKey); + $dbToken->setPrivateKey($this->encrypt($privateKey, $token)); + + if (!is_null($password)) { + $dbToken->setPassword($this->encryptPassword($password, $publicKey)); + } + + $dbToken->setName($name); + $dbToken->setToken($this->hashToken($token)); + $dbToken->setType($type); + $dbToken->setRemember($remember); + $dbToken->setLastActivity($this->time->getTime()); + $dbToken->setLastCheck($this->time->getTime()); + $dbToken->setVersion(PublicKeyToken::VERSION); + + return $dbToken; + } + + public function markPasswordInvalid(IToken $token, string $tokenId) { + $this->cache->clear(); + + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException("Invalid token type"); + } + + $token->setPasswordInvalid(true); + $this->mapper->update($token); + } + + public function updatePasswords(string $uid, string $password) { + $this->cache->clear(); + + if (!$this->mapper->hasExpiredTokens($uid)) { + // Nothing to do here + return; + } + + // Update the password for all tokens + $tokens = $this->mapper->getTokenByUser($uid); + foreach ($tokens as $t) { + $publicKey = $t->getPublicKey(); + $t->setPassword($this->encryptPassword($password, $publicKey)); + $t->setPasswordInvalid(false); + $this->updateToken($t); + } + } + + private function logOpensslError() { + $errors = []; + while ($error = openssl_error_string()) { + $errors[] = $error; + } + $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/Token/RemoteWipe.php b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/RemoteWipe.php new file mode 100644 index 0000000..cab6835 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/Token/RemoteWipe.php @@ -0,0 +1,155 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\Token; + +use function array_filter; +use OC\Authentication\Events\RemoteWipeFinished; +use OC\Authentication\Events\RemoteWipeStarted; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\WipeTokenException; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\ILogger; +use OCP\IUser; + +class RemoteWipe { + + /** @var IProvider */ + private $tokenProvider; + + /** @var IEventDispatcher */ + private $eventDispatcher; + + /** @var ILogger */ + private $logger; + + public function __construct(IProvider $tokenProvider, + IEventDispatcher $eventDispatcher, + ILogger $logger) { + $this->tokenProvider = $tokenProvider; + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; + } + + /** + * @param IToken $token + * @return bool + * + * @throws InvalidTokenException + * @throws WipeTokenException + */ + public function markTokenForWipe(IToken $token): bool { + if (!$token instanceof IWipeableToken) { + return false; + } + + $token->wipe(); + $this->tokenProvider->updateToken($token); + + return true; + } + + /** + * @param IUser $user + * + * @return bool true if any tokens have been marked for remote wipe + */ + public function markAllTokensForWipe(IUser $user): bool { + $tokens = $this->tokenProvider->getTokenByUser($user->getUID()); + + /** @var IWipeableToken[] $wipeable */ + $wipeable = array_filter($tokens, function (IToken $token) { + return $token instanceof IWipeableToken; + }); + + if (empty($wipeable)) { + return false; + } + + foreach ($wipeable as $token) { + $token->wipe(); + $this->tokenProvider->updateToken($token); + } + + return true; + } + + /** + * @param string $token + * + * @return bool whether wiping was started + * @throws InvalidTokenException + * + */ + public function start(string $token): bool { + try { + $this->tokenProvider->getToken($token); + + // We expect a WipedTokenException here. If we reach this point this + // is an ordinary token + return false; + } catch (WipeTokenException $e) { + // Expected -> continue below + } + + $dbToken = $e->getToken(); + + $this->logger->info("user " . $dbToken->getUID() . " started a remote wipe"); + + $this->eventDispatcher->dispatch(RemoteWipeStarted::class, new RemoteWipeStarted($dbToken)); + + return true; + } + + /** + * @param string $token + * + * @return bool whether wiping could be finished + * @throws InvalidTokenException + */ + public function finish(string $token): bool { + try { + $this->tokenProvider->getToken($token); + + // We expect a WipedTokenException here. If we reach this point this + // is an ordinary token + return false; + } catch (WipeTokenException $e) { + // Expected -> continue below + } + + $dbToken = $e->getToken(); + + $this->tokenProvider->invalidateToken($token); + + $this->logger->info("user " . $dbToken->getUID() . " finished a remote wipe"); + $this->eventDispatcher->dispatch(RemoteWipeFinished::class, new RemoteWipeFinished($dbToken)); + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php new file mode 100644 index 0000000..bd8ff03 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php @@ -0,0 +1,134 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth\Db; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use function array_map; + +/** + * Data access object to query and assign (provider_id, uid, enabled) tuples of + * 2FA providers + */ +class ProviderUserAssignmentDao { + public const TABLE_NAME = 'twofactor_providers'; + + /** @var IDBConnection */ + private $conn; + + public function __construct(IDBConnection $dbConn) { + $this->conn = $dbConn; + } + + /** + * Get all assigned provider IDs for the given user ID + * + * @return string[] where the array key is the provider ID (string) and the + * value is the enabled state (bool) + */ + public function getState(string $uid): array { + $qb = $this->conn->getQueryBuilder(); + + $query = $qb->select('provider_id', 'enabled') + ->from(self::TABLE_NAME) + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))); + $result = $query->execute(); + $providers = []; + foreach ($result->fetchAll() as $row) { + $providers[$row['provider_id']] = 1 === (int)$row['enabled']; + } + $result->closeCursor(); + + return $providers; + } + + /** + * Persist a new/updated (provider_id, uid, enabled) tuple + */ + public function persist(string $providerId, string $uid, int $enabled) { + $qb = $this->conn->getQueryBuilder(); + + try { + // Insert a new entry + $insertQuery = $qb->insert(self::TABLE_NAME)->values([ + 'provider_id' => $qb->createNamedParameter($providerId), + 'uid' => $qb->createNamedParameter($uid), + 'enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_INT), + ]); + + $insertQuery->execute(); + } catch (UniqueConstraintViolationException $ex) { + // There is already an entry -> update it + $updateQuery = $qb->update(self::TABLE_NAME) + ->set('enabled', $qb->createNamedParameter($enabled)) + ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid))); + $updateQuery->execute(); + } + } + + /** + * Delete all provider states of a user and return the provider IDs + * + * @param string $uid + * + * @return int[] + */ + public function deleteByUser(string $uid): array { + $qb1 = $this->conn->getQueryBuilder(); + $selectQuery = $qb1->select('*') + ->from(self::TABLE_NAME) + ->where($qb1->expr()->eq('uid', $qb1->createNamedParameter($uid))); + $selectResult = $selectQuery->execute(); + $rows = $selectResult->fetchAll(); + $selectResult->closeCursor(); + + $qb2 = $this->conn->getQueryBuilder(); + $deleteQuery = $qb2 + ->delete(self::TABLE_NAME) + ->where($qb2->expr()->eq('uid', $qb2->createNamedParameter($uid))); + $deleteQuery->execute(); + + return array_map(function (array $row) { + return [ + 'provider_id' => $row['provider_id'], + 'uid' => $row['uid'], + 'enabled' => 1 === (int) $row['enabled'], + ]; + }, $rows); + } + + public function deleteAll(string $providerId) { + $qb = $this->conn->getQueryBuilder(); + + $deleteQuery = $qb->delete(self::TABLE_NAME) + ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId))); + + $deleteQuery->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/EnforcementState.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/EnforcementState.php new file mode 100644 index 0000000..c244843 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/EnforcementState.php @@ -0,0 +1,85 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use JsonSerializable; + +class EnforcementState implements JsonSerializable { + + /** @var bool */ + private $enforced; + + /** @var array */ + private $enforcedGroups; + + /** @var array */ + private $excludedGroups; + + /** + * EnforcementState constructor. + * + * @param bool $enforced + * @param string[] $enforcedGroups + * @param string[] $excludedGroups + */ + public function __construct(bool $enforced, + array $enforcedGroups = [], + array $excludedGroups = []) { + $this->enforced = $enforced; + $this->enforcedGroups = $enforcedGroups; + $this->excludedGroups = $excludedGroups; + } + + /** + * @return bool + */ + public function isEnforced(): bool { + return $this->enforced; + } + + /** + * @return string[] + */ + public function getEnforcedGroups(): array { + return $this->enforcedGroups; + } + + /** + * @return string[] + */ + public function getExcludedGroups(): array { + return $this->excludedGroups; + } + + public function jsonSerialize(): array { + return [ + 'enforced' => $this->enforced, + 'enforcedGroups' => $this->enforcedGroups, + 'excludedGroups' => $this->excludedGroups, + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Manager.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Manager.php new file mode 100644 index 0000000..07e6117 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Manager.php @@ -0,0 +1,385 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use function array_diff; +use function array_filter; +use BadMethodCallException; +use Exception; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IProvider as TokenProvider; +use OCP\Activity\IManager; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\IConfig; +use OCP\ILogger; +use OCP\ISession; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Manager { + public const SESSION_UID_KEY = 'two_factor_auth_uid'; + public const SESSION_UID_DONE = 'two_factor_auth_passed'; + public const REMEMBER_LOGIN = 'two_factor_remember_login'; + public const BACKUP_CODES_PROVIDER_ID = 'backup_codes'; + + /** @var ProviderLoader */ + private $providerLoader; + + /** @var IRegistry */ + private $providerRegistry; + + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + + /** @var ISession */ + private $session; + + /** @var IConfig */ + private $config; + + /** @var IManager */ + private $activityManager; + + /** @var ILogger */ + private $logger; + + /** @var TokenProvider */ + private $tokenProvider; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + public function __construct(ProviderLoader $providerLoader, + IRegistry $providerRegistry, + MandatoryTwoFactor $mandatoryTwoFactor, + ISession $session, IConfig $config, + IManager $activityManager, ILogger $logger, TokenProvider $tokenProvider, + ITimeFactory $timeFactory, EventDispatcherInterface $eventDispatcher) { + $this->providerLoader = $providerLoader; + $this->providerRegistry = $providerRegistry; + $this->mandatoryTwoFactor = $mandatoryTwoFactor; + $this->session = $session; + $this->config = $config; + $this->activityManager = $activityManager; + $this->logger = $logger; + $this->tokenProvider = $tokenProvider; + $this->timeFactory = $timeFactory; + $this->dispatcher = $eventDispatcher; + } + + /** + * Determine whether the user must provide a second factor challenge + * + * @param IUser $user + * @return boolean + */ + public function isTwoFactorAuthenticated(IUser $user): bool { + if ($this->mandatoryTwoFactor->isEnforcedFor($user)) { + return true; + } + + $providerStates = $this->providerRegistry->getProviderStates($user); + $providers = $this->providerLoader->getProviders($user); + $fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user); + $enabled = array_filter($fixedStates); + $providerIds = array_keys($enabled); + $providerIdsWithoutBackupCodes = array_diff($providerIds, [self::BACKUP_CODES_PROVIDER_ID]); + + return !empty($providerIdsWithoutBackupCodes); + } + + /** + * Get a 2FA provider by its ID + * + * @param IUser $user + * @param string $challengeProviderId + * @return IProvider|null + */ + public function getProvider(IUser $user, string $challengeProviderId) { + $providers = $this->getProviderSet($user)->getProviders(); + return $providers[$challengeProviderId] ?? null; + } + + /** + * @param IUser $user + * @return IActivatableAtLogin[] + * @throws Exception + */ + public function getLoginSetupProviders(IUser $user): array { + $providers = $this->providerLoader->getProviders($user); + return array_filter($providers, function (IProvider $provider) { + return ($provider instanceof IActivatableAtLogin); + }); + } + + /** + * Check if the persistant mapping of enabled/disabled state of each available + * provider is missing an entry and add it to the registry in that case. + * + * @todo remove in Nextcloud 17 as by then all providers should have been updated + * + * @param string[] $providerStates + * @param IProvider[] $providers + * @param IUser $user + * @return string[] the updated $providerStates variable + */ + private function fixMissingProviderStates(array $providerStates, + array $providers, IUser $user): array { + foreach ($providers as $provider) { + if (isset($providerStates[$provider->getId()])) { + // All good + continue; + } + + $enabled = $provider->isTwoFactorAuthEnabledForUser($user); + if ($enabled) { + $this->providerRegistry->enableProviderFor($provider, $user); + } else { + $this->providerRegistry->disableProviderFor($provider, $user); + } + $providerStates[$provider->getId()] = $enabled; + } + + return $providerStates; + } + + /** + * @param array $states + * @param IProvider[] $providers + */ + private function isProviderMissing(array $states, array $providers): bool { + $indexed = []; + foreach ($providers as $provider) { + $indexed[$provider->getId()] = $provider; + } + + $missing = []; + foreach ($states as $providerId => $enabled) { + if (!$enabled) { + // Don't care + continue; + } + + if (!isset($indexed[$providerId])) { + $missing[] = $providerId; + $this->logger->alert("two-factor auth provider '$providerId' failed to load", + [ + 'app' => 'core', + ]); + } + } + + if (!empty($missing)) { + // There was at least one provider missing + $this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']); + + return true; + } + + // If we reach this, there was not a single provider missing + return false; + } + + /** + * Get the list of 2FA providers for the given user + * + * @param IUser $user + * @throws Exception + */ + public function getProviderSet(IUser $user): ProviderSet { + $providerStates = $this->providerRegistry->getProviderStates($user); + $providers = $this->providerLoader->getProviders($user); + + $fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user); + $isProviderMissing = $this->isProviderMissing($fixedStates, $providers); + + $enabled = array_filter($providers, function (IProvider $provider) use ($fixedStates) { + return $fixedStates[$provider->getId()]; + }); + return new ProviderSet($enabled, $isProviderMissing); + } + + /** + * Verify the given challenge + * + * @param string $providerId + * @param IUser $user + * @param string $challenge + * @return boolean + */ + public function verifyChallenge(string $providerId, IUser $user, string $challenge): bool { + $provider = $this->getProvider($user, $providerId); + if ($provider === null) { + return false; + } + + $passed = $provider->verifyChallenge($user, $challenge); + if ($passed) { + if ($this->session->get(self::REMEMBER_LOGIN) === true) { + // TODO: resolve cyclic dependency and use DI + \OC::$server->getUserSession()->createRememberMeToken($user); + } + $this->session->remove(self::SESSION_UID_KEY); + $this->session->remove(self::REMEMBER_LOGIN); + $this->session->set(self::SESSION_UID_DONE, $user->getUID()); + + // Clear token from db + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + $tokenId = $token->getId(); + $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $tokenId); + + $dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]); + $this->dispatcher->dispatch(IProvider::EVENT_SUCCESS, $dispatchEvent); + + $this->publishEvent($user, 'twofactor_success', [ + 'provider' => $provider->getDisplayName(), + ]); + } else { + $dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]); + $this->dispatcher->dispatch(IProvider::EVENT_FAILED, $dispatchEvent); + + $this->publishEvent($user, 'twofactor_failed', [ + 'provider' => $provider->getDisplayName(), + ]); + } + return $passed; + } + + /** + * Push a 2fa event the user's activity stream + * + * @param IUser $user + * @param string $event + * @param array $params + */ + private function publishEvent(IUser $user, string $event, array $params) { + $activity = $this->activityManager->generateEvent(); + $activity->setApp('core') + ->setType('security') + ->setAuthor($user->getUID()) + ->setAffectedUser($user->getUID()) + ->setSubject($event, $params); + try { + $this->activityManager->publish($activity); + } catch (BadMethodCallException $e) { + $this->logger->warning('could not publish activity', ['app' => 'core']); + $this->logger->logException($e, ['app' => 'core']); + } + } + + /** + * Check if the currently logged in user needs to pass 2FA + * + * @param IUser $user the currently logged in user + * @return boolean + */ + public function needsSecondFactor(IUser $user = null): bool { + if ($user === null) { + return false; + } + + // If we are authenticated using an app password skip all this + if ($this->session->exists('app_password')) { + return false; + } + + // First check if the session tells us we should do 2FA (99% case) + if (!$this->session->exists(self::SESSION_UID_KEY)) { + + // Check if the session tells us it is 2FA authenticated already + if ($this->session->exists(self::SESSION_UID_DONE) && + $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) { + return false; + } + + /* + * If the session is expired check if we are not logged in by a token + * that still needs 2FA auth + */ + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + $tokenId = $token->getId(); + $tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa'); + + if (!\in_array($tokenId, $tokensNeeding2FA, true)) { + $this->session->set(self::SESSION_UID_DONE, $user->getUID()); + return false; + } + } catch (InvalidTokenException $e) { + } + } + + if (!$this->isTwoFactorAuthenticated($user)) { + // There is no second factor any more -> let the user pass + // This prevents infinite redirect loops when a user is about + // to solve the 2FA challenge, and the provider app is + // disabled the same time + $this->session->remove(self::SESSION_UID_KEY); + + $keys = $this->config->getUserKeys($user->getUID(), 'login_token_2fa'); + foreach ($keys as $key) { + $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $key); + } + return false; + } + + return true; + } + + /** + * Prepare the 2FA login + * + * @param IUser $user + * @param boolean $rememberMe + */ + public function prepareTwoFactorLogin(IUser $user, bool $rememberMe) { + $this->session->set(self::SESSION_UID_KEY, $user->getUID()); + $this->session->set(self::REMEMBER_LOGIN, $rememberMe); + + $id = $this->session->getId(); + $token = $this->tokenProvider->getToken($id); + $this->config->setUserValue($user->getUID(), 'login_token_2fa', $token->getId(), $this->timeFactory->getTime()); + } + + public function clearTwoFactorPending(string $userId) { + $tokensNeeding2FA = $this->config->getUserKeys($userId, 'login_token_2fa'); + + foreach ($tokensNeeding2FA as $tokenId) { + $this->tokenProvider->invalidateTokenById($userId, $tokenId); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php new file mode 100644 index 0000000..b06a517 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php @@ -0,0 +1,113 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; + +class MandatoryTwoFactor { + + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + public function __construct(IConfig $config, IGroupManager $groupManager) { + $this->config = $config; + $this->groupManager = $groupManager; + } + + /** + * Get the state of enforced two-factor auth + */ + public function getState(): EnforcementState { + return new EnforcementState( + $this->config->getSystemValue('twofactor_enforced', 'false') === 'true', + $this->config->getSystemValue('twofactor_enforced_groups', []), + $this->config->getSystemValue('twofactor_enforced_excluded_groups', []) + ); + } + + /** + * Set the state of enforced two-factor auth + */ + public function setState(EnforcementState $state) { + $this->config->setSystemValue('twofactor_enforced', $state->isEnforced() ? 'true' : 'false'); + $this->config->setSystemValue('twofactor_enforced_groups', $state->getEnforcedGroups()); + $this->config->setSystemValue('twofactor_enforced_excluded_groups', $state->getExcludedGroups()); + } + + /** + * Check if two-factor auth is enforced for a specific user + * + * The admin(s) can enforce two-factor auth system-wide, for certain groups only + * and also have the option to exclude users of certain groups. This method will + * check their membership of those groups. + * + * @param IUser $user + * + * @return bool + */ + public function isEnforcedFor(IUser $user): bool { + $state = $this->getState(); + if (!$state->isEnforced()) { + return false; + } + $uid = $user->getUID(); + + /* + * If there is a list of enforced groups, we only enforce 2FA for members of those groups. + * For all the other users it is not enforced (overruling the excluded groups list). + */ + if (!empty($state->getEnforcedGroups())) { + foreach ($state->getEnforcedGroups() as $group) { + if ($this->groupManager->isInGroup($uid, $group)) { + return true; + } + } + // Not a member of any of these groups -> no 2FA enforced + return false; + } + + /** + * If the user is member of an excluded group, 2FA won't be enforced. + */ + foreach ($state->getExcludedGroups() as $group) { + if ($this->groupManager->isInGroup($uid, $group)) { + return false; + } + } + + /** + * No enforced groups configured and user not member of an excluded groups, + * so 2FA is enforced. + */ + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php new file mode 100644 index 0000000..40385d7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php @@ -0,0 +1,88 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use Exception; +use OC; +use OC_App; +use OCP\App\IAppManager; +use OCP\AppFramework\QueryException; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\IUser; + +class ProviderLoader { + public const BACKUP_CODES_APP_ID = 'twofactor_backupcodes'; + + /** @var IAppManager */ + private $appManager; + + public function __construct(IAppManager $appManager) { + $this->appManager = $appManager; + } + + /** + * Get the list of 2FA providers for the given user + * + * @return IProvider[] + * @throws Exception + */ + public function getProviders(IUser $user): array { + $allApps = $this->appManager->getEnabledAppsForUser($user); + $providers = []; + + foreach ($allApps as $appId) { + $info = $this->appManager->getAppInfo($appId); + if (isset($info['two-factor-providers'])) { + /** @var string[] $providerClasses */ + $providerClasses = $info['two-factor-providers']; + foreach ($providerClasses as $class) { + try { + $this->loadTwoFactorApp($appId); + $provider = OC::$server->query($class); + $providers[$provider->getId()] = $provider; + } catch (QueryException $exc) { + // Provider class can not be resolved + throw new Exception("Could not load two-factor auth provider $class"); + } + } + } + } + + return $providers; + } + + /** + * Load an app by ID if it has not been loaded yet + * + * @param string $appId + */ + protected function loadTwoFactorApp(string $appId) { + if (!OC_App::isAppLoaded($appId)) { + OC_App::loadApp($appId); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderManager.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderManager.php new file mode 100644 index 0000000..4d3171e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderManager.php @@ -0,0 +1,96 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use OC\Authentication\Exceptions\InvalidProviderException; +use OCP\Authentication\TwoFactorAuth\IActivatableByAdmin; +use OCP\Authentication\TwoFactorAuth\IDeactivatableByAdmin; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\IUser; + +class ProviderManager { + + /** @var ProviderLoader */ + private $providerLoader; + + /** @var IRegistry */ + private $providerRegistry; + + public function __construct(ProviderLoader $providerLoader, IRegistry $providerRegistry) { + $this->providerLoader = $providerLoader; + $this->providerRegistry = $providerRegistry; + } + + private function getProvider(string $providerId, IUser $user): IProvider { + $providers = $this->providerLoader->getProviders($user); + + if (!isset($providers[$providerId])) { + throw new InvalidProviderException($providerId); + } + + return $providers[$providerId]; + } + + /** + * Try to enable the provider with the given id for the given user + * + * @param IUser $user + * + * @return bool whether the provider supports this operation + */ + public function tryEnableProviderFor(string $providerId, IUser $user): bool { + $provider = $this->getProvider($providerId, $user); + + if ($provider instanceof IActivatableByAdmin) { + $provider->enableFor($user); + $this->providerRegistry->enableProviderFor($provider, $user); + return true; + } else { + return false; + } + } + + /** + * Try to disable the provider with the given id for the given user + * + * @param IUser $user + * + * @return bool whether the provider supports this operation + */ + public function tryDisableProviderFor(string $providerId, IUser $user): bool { + $provider = $this->getProvider($providerId, $user); + + if ($provider instanceof IDeactivatableByAdmin) { + $provider->disableFor($user); + $this->providerRegistry->disableProviderFor($provider, $user); + return true; + } else { + return false; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderSet.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderSet.php new file mode 100644 index 0000000..c27681c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/ProviderSet.php @@ -0,0 +1,83 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use function array_filter; +use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider; +use OCP\Authentication\TwoFactorAuth\IProvider; + +/** + * Contains all two-factor provider information for the two-factor login challenge + */ +class ProviderSet { + + /** @var IProvider */ + private $providers; + + /** @var bool */ + private $providerMissing; + + /** + * @param IProvider[] $providers + * @param bool $providerMissing + */ + public function __construct(array $providers, bool $providerMissing) { + $this->providers = []; + foreach ($providers as $provider) { + $this->providers[$provider->getId()] = $provider; + } + $this->providerMissing = $providerMissing; + } + + /** + * @param string $providerId + * @return IProvider|null + */ + public function getProvider(string $providerId) { + return $this->providers[$providerId] ?? null; + } + + /** + * @return IProvider[] + */ + public function getProviders(): array { + return $this->providers; + } + + /** + * @return IProvider[] + */ + public function getPrimaryProviders(): array { + return array_filter($this->providers, function (IProvider $provider) { + return !($provider instanceof BackupCodesProvider); + }); + } + + public function isProviderMissing(): bool { + return $this->providerMissing; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Registry.php b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Registry.php new file mode 100644 index 0000000..2af8566 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/TwoFactorAuth/Registry.php @@ -0,0 +1,80 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IUser; + +class Registry implements IRegistry { + + /** @var ProviderUserAssignmentDao */ + private $assignmentDao; + + /** @var IEventDispatcher */ + private $dispatcher; + + public function __construct(ProviderUserAssignmentDao $assignmentDao, + IEventDispatcher $dispatcher) { + $this->assignmentDao = $assignmentDao; + $this->dispatcher = $dispatcher; + } + + public function getProviderStates(IUser $user): array { + return $this->assignmentDao->getState($user->getUID()); + } + + public function enableProviderFor(IProvider $provider, IUser $user) { + $this->assignmentDao->persist($provider->getId(), $user->getUID(), 1); + + $event = new RegistryEvent($provider, $user); + $this->dispatcher->dispatch(self::EVENT_PROVIDER_ENABLED, $event); + } + + public function disableProviderFor(IProvider $provider, IUser $user) { + $this->assignmentDao->persist($provider->getId(), $user->getUID(), 0); + + $event = new RegistryEvent($provider, $user); + $this->dispatcher->dispatch(self::EVENT_PROVIDER_DISABLED, $event); + } + + public function deleteUserData(IUser $user): void { + foreach ($this->assignmentDao->deleteByUser($user->getUID()) as $provider) { + $event = new TwoFactorProviderDisabled($provider['provider_id']); + $this->dispatcher->dispatchTyped($event); + } + } + + public function cleanUp(string $providerId) { + $this->assignmentDao->deleteAll($providerId); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/CredentialRepository.php b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/CredentialRepository.php new file mode 100644 index 0000000..f75b4b5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/CredentialRepository.php @@ -0,0 +1,95 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\WebAuthn; + +use OC\Authentication\WebAuthn\Db\PublicKeyCredentialEntity; +use OC\Authentication\WebAuthn\Db\PublicKeyCredentialMapper; +use OCP\AppFramework\Db\IMapperException; +use Webauthn\PublicKeyCredentialSource; +use Webauthn\PublicKeyCredentialSourceRepository; +use Webauthn\PublicKeyCredentialUserEntity; + +class CredentialRepository implements PublicKeyCredentialSourceRepository { + + /** @var PublicKeyCredentialMapper */ + private $credentialMapper; + + public function __construct(PublicKeyCredentialMapper $credentialMapper) { + $this->credentialMapper = $credentialMapper; + } + + public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource { + try { + $entity = $this->credentialMapper->findOneByCredentialId($publicKeyCredentialId); + return $entity->toPublicKeyCredentialSource(); + } catch (IMapperException $e) { + return null; + } + } + + /** + * @return PublicKeyCredentialSource[] + */ + public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { + $uid = $publicKeyCredentialUserEntity->getId(); + $entities = $this->credentialMapper->findAllForUid($uid); + + return array_map(function (PublicKeyCredentialEntity $entity) { + return $entity->toPublicKeyCredentialSource(); + }, $entities); + } + + public function saveAndReturnCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, string $name = null): PublicKeyCredentialEntity { + $oldEntity = null; + + try { + $oldEntity = $this->credentialMapper->findOneByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId()); + } catch (IMapperException $e) { + } + + $defaultName = false; + if ($name === null) { + $defaultName = true; + $name = 'default'; + } + + $entity = PublicKeyCredentialEntity::fromPublicKeyCrendentialSource($name, $publicKeyCredentialSource); + + if ($oldEntity) { + $entity->setId($oldEntity->getId()); + if ($defaultName) { + $entity->setName($oldEntity->getName()); + } + } + + return $this->credentialMapper->insertOrUpdate($entity); + } + + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, string $name = null): void { + $this->saveAndReturnCredentialSource($publicKeyCredentialSource, $name); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php new file mode 100644 index 0000000..ae31a2e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php @@ -0,0 +1,93 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\WebAuthn\Db; + +use JsonSerializable; +use OCP\AppFramework\Db\Entity; +use Webauthn\PublicKeyCredentialSource; + +/** + * @since 19.0.0 + * + * @method string getUid(); + * @method void setUid(string $uid) + * @method string getName(); + * @method void setName(string $name); + * @method string getPublicKeyCredentialId(); + * @method void setPublicKeyCredentialId(string $id); + * @method string getData(); + * @method void setData(string $data); + */ +class PublicKeyCredentialEntity extends Entity implements JsonSerializable { + + /** @var string */ + protected $name; + + /** @var string */ + protected $uid; + + /** @var string */ + protected $publicKeyCredentialId; + + /** @var string */ + protected $data; + + public function __construct() { + $this->addType('name', 'string'); + $this->addType('uid', 'string'); + $this->addType('publicKeyCredentialId', 'string'); + $this->addType('data', 'string'); + } + + public static function fromPublicKeyCrendentialSource(string $name, PublicKeyCredentialSource $publicKeyCredentialSource): PublicKeyCredentialEntity { + $publicKeyCredentialEntity = new self(); + + $publicKeyCredentialEntity->setName($name); + $publicKeyCredentialEntity->setUid($publicKeyCredentialSource->getUserHandle()); + $publicKeyCredentialEntity->setPublicKeyCredentialId(base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId())); + $publicKeyCredentialEntity->setData(json_encode($publicKeyCredentialSource)); + + return $publicKeyCredentialEntity; + } + + public function toPublicKeyCredentialSource(): PublicKeyCredentialSource { + return PublicKeyCredentialSource::createFromArray( + json_decode($this->getData(), true) + ); + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'name' => $this->getName(), + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php new file mode 100644 index 0000000..9c436b3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php @@ -0,0 +1,84 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\WebAuthn\Db; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\IDBConnection; + +class PublicKeyCredentialMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'webauthn', PublicKeyCredentialEntity::class); + } + + public function findOneByCredentialId(string $publicKeyCredentialId): PublicKeyCredentialEntity { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('public_key_credential_id', $qb->createNamedParameter(base64_encode($publicKeyCredentialId))) + ); + + return $this->findEntity($qb); + } + + /** + * @return PublicKeyCredentialEntity[] + */ + public function findAllForUid(string $uid): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('uid', $qb->createNamedParameter($uid)) + ); + + return $this->findEntities($qb); + } + + /** + * @param string $uid + * @param int $id + * + * @return PublicKeyCredentialEntity + * @throws DoesNotExistException + */ + public function findById(string $uid, int $id): PublicKeyCredentialEntity { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->andX( + $qb->expr()->eq('id', $qb->createNamedParameter($id)), + $qb->expr()->eq('uid', $qb->createNamedParameter($uid)) + )); + + return $this->findEntity($qb); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Manager.php b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Manager.php new file mode 100644 index 0000000..4415bad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Authentication/WebAuthn/Manager.php @@ -0,0 +1,275 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Authentication\WebAuthn; + +use Cose\Algorithm\Signature\ECDSA\ES256; +use Cose\Algorithm\Signature\RSA\RS256; +use Cose\Algorithms; +use GuzzleHttp\Psr7\ServerRequest; +use OC\Authentication\WebAuthn\Db\PublicKeyCredentialEntity; +use OC\Authentication\WebAuthn\Db\PublicKeyCredentialMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use Webauthn\AttestationStatement\AttestationObjectLoader; +use Webauthn\AttestationStatement\AttestationStatementSupportManager; +use Webauthn\AttestationStatement\NoneAttestationStatementSupport; +use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; +use Webauthn\AuthenticatorAssertionResponse; +use Webauthn\AuthenticatorAssertionResponseValidator; +use Webauthn\AuthenticatorAttestationResponse; +use Webauthn\AuthenticatorAttestationResponseValidator; +use Webauthn\AuthenticatorSelectionCriteria; +use Webauthn\PublicKeyCredentialCreationOptions; +use Webauthn\PublicKeyCredentialDescriptor; +use Webauthn\PublicKeyCredentialLoader; +use Webauthn\PublicKeyCredentialParameters; +use Webauthn\PublicKeyCredentialRequestOptions; +use Webauthn\PublicKeyCredentialRpEntity; +use Webauthn\PublicKeyCredentialUserEntity; +use Webauthn\TokenBinding\TokenBindingNotSupportedHandler; + +class Manager { + + /** @var CredentialRepository */ + private $repository; + + /** @var PublicKeyCredentialMapper */ + private $credentialMapper; + + /** @var ILogger */ + private $logger; + + /** @var IConfig */ + private $config; + + public function __construct( + CredentialRepository $repository, + PublicKeyCredentialMapper $credentialMapper, + ILogger $logger, + IConfig $config + ) { + $this->repository = $repository; + $this->credentialMapper = $credentialMapper; + $this->logger = $logger; + $this->config = $config; + } + + public function startRegistration(IUser $user, string $serverHost): PublicKeyCredentialCreationOptions { + $rpEntity = new PublicKeyCredentialRpEntity( + 'Nextcloud', //Name + $this->stripPort($serverHost), //ID + null //Icon + ); + + $userEntity = new PublicKeyCredentialUserEntity( + $user->getUID(), //Name + $user->getUID(), //ID + $user->getDisplayName() //Display name +// 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon + ); + + $challenge = random_bytes(32); + + $publicKeyCredentialParametersList = [ + new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256), + new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_RS256), + ]; + + $timeout = 60000; + + $excludedPublicKeyDescriptors = [ + ]; + + $authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria( + null, + false, + AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED + ); + + return new PublicKeyCredentialCreationOptions( + $rpEntity, + $userEntity, + $challenge, + $publicKeyCredentialParametersList, + $timeout, + $excludedPublicKeyDescriptors, + $authenticatorSelectionCriteria, + PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, + null + ); + } + + public function finishRegister(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, string $name, string $data): PublicKeyCredentialEntity { + $tokenBindingHandler = new TokenBindingNotSupportedHandler(); + + $attestationStatementSupportManager = new AttestationStatementSupportManager(); + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + + $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); + $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); + + // Extension Output Checker Handler + $extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); + + // Authenticator Attestation Response Validator + $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( + $attestationStatementSupportManager, + $this->repository, + $tokenBindingHandler, + $extensionOutputCheckerHandler + ); + + try { + // Load the data + $publicKeyCredential = $publicKeyCredentialLoader->load($data); + $response = $publicKeyCredential->getResponse(); + + // Check if the response is an Authenticator Attestation Response + if (!$response instanceof AuthenticatorAttestationResponse) { + throw new \RuntimeException('Not an authenticator attestation response'); + } + + // Check the response against the request + $request = ServerRequest::fromGlobals(); + + $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( + $response, + $publicKeyCredentialCreationOptions, + $request); + } catch (\Throwable $exception) { + throw $exception; + } + + // Persist the data + return $this->repository->saveAndReturnCredentialSource($publicKeyCredentialSource, $name); + } + + private function stripPort(string $serverHost): string { + return preg_replace('/(:\d+$)/', '', $serverHost); + } + + public function startAuthentication(string $uid, string $serverHost): PublicKeyCredentialRequestOptions { + // List of registered PublicKeyCredentialDescriptor classes associated to the user + $registeredPublicKeyCredentialDescriptors = array_map(function (PublicKeyCredentialEntity $entity) { + $credential = $entity->toPublicKeyCredentialSource(); + return new PublicKeyCredentialDescriptor( + $credential->getType(), + $credential->getPublicKeyCredentialId() + ); + }, $this->credentialMapper->findAllForUid($uid)); + + // Public Key Credential Request Options + return new PublicKeyCredentialRequestOptions( + random_bytes(32), // Challenge + 60000, // Timeout + $this->stripPort($serverHost), // Relying Party ID + $registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes + AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED + ); + } + + public function finishAuthentication(PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, string $data, string $uid) { + $attestationStatementSupportManager = new AttestationStatementSupportManager(); + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + + $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); + $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); + + $tokenBindingHandler = new TokenBindingNotSupportedHandler(); + $extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); + $algorithmManager = new \Cose\Algorithm\Manager(); + $algorithmManager->add(new ES256()); + $algorithmManager->add(new RS256()); + + $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( + $this->repository, + $tokenBindingHandler, + $extensionOutputCheckerHandler, + $algorithmManager + ); + + try { + $this->logger->debug('Loading publickey credentials from: ' . $data); + + // Load the data + $publicKeyCredential = $publicKeyCredentialLoader->load($data); + $response = $publicKeyCredential->getResponse(); + + // Check if the response is an Authenticator Attestation Response + if (!$response instanceof AuthenticatorAssertionResponse) { + throw new \RuntimeException('Not an authenticator attestation response'); + } + + // Check the response against the request + $request = ServerRequest::fromGlobals(); + + $publicKeyCredentialSource = $authenticatorAssertionResponseValidator->check( + $publicKeyCredential->getRawId(), + $response, + $publicKeyCredentialRequestOptions, + $request, + $uid + ); + } catch (\Throwable $e) { + throw $e; + } + + + + return true; + } + + public function deleteRegistration(IUser $user, int $id): void { + try { + $entry = $this->credentialMapper->findById($user->getUID(), $id); + } catch (DoesNotExistException $e) { + $this->logger->warning("WebAuthn device $id does not exist, can't delete it"); + return; + } + + $this->credentialMapper->delete($entry); + } + + public function isWebAuthnAvailable(): bool { + if (!extension_loaded('bcmath')) { + return false; + } + + if (!extension_loaded('gmp')) { + return false; + } + + if (!$this->config->getSystemValueBool('auth.webauthn.enabled', true)) { + return false; + } + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Avatar/Avatar.php b/docker/overlays/nextcloud/html/lib/private/Avatar/Avatar.php new file mode 100644 index 0000000..02fc04e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Avatar/Avatar.php @@ -0,0 +1,326 @@ + + * + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Weimann + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sergey Shliakhov + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Avatar; + +use Imagick; +use OC\Color; +use OC_Image; +use OCP\Files\NotFoundException; +use OCP\IAvatar; +use OCP\ILogger; + +/** + * This class gets and sets users avatars. + */ +abstract class Avatar implements IAvatar { + + /** @var ILogger */ + protected $logger; + + /** + * https://github.com/sebdesign/cap-height -- for 500px height + * Automated check: https://codepen.io/skjnldsv/pen/PydLBK/ + * Noto Sans cap-height is 0.715 and we want a 200px caps height size + * (0.4 letter-to-total-height ratio, 500*0.4=200), so: 200/0.715 = 280px. + * Since we start from the baseline (text-anchor) we need to + * shift the y axis by 100px (half the caps height): 500/2+100=350 + * + * @var string + */ + private $svgTemplate = ' + + + {letter} + '; + + /** + * The base avatar constructor. + * + * @param ILogger $logger The logger + */ + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + /** + * Returns the user display name. + * + * @return string + */ + abstract public function getDisplayName(): string; + + /** + * Returns the first letter of the display name, or "?" if no name given. + * + * @return string + */ + private function getAvatarText(): string { + $displayName = $this->getDisplayName(); + if (empty($displayName) === true) { + return '?'; + } + $firstTwoLetters = array_map(function ($namePart) { + return mb_strtoupper(mb_substr($namePart, 0, 1), 'UTF-8'); + }, explode(' ', $displayName, 2)); + return implode('', $firstTwoLetters); + } + + /** + * @inheritdoc + */ + public function get($size = 64) { + $size = (int) $size; + + try { + $file = $this->getFile($size); + } catch (NotFoundException $e) { + return false; + } + + $avatar = new OC_Image(); + $avatar->loadFromData($file->getContent()); + return $avatar; + } + + /** + * {size} = 500 + * {fill} = hex color to fill + * {letter} = Letter to display + * + * Generate SVG avatar + * + * @param int $size The requested image size in pixel + * @return string + * + */ + protected function getAvatarVector(int $size): string { + $userDisplayName = $this->getDisplayName(); + $bgRGB = $this->avatarBackgroundColor($userDisplayName); + $bgHEX = sprintf("%02x%02x%02x", $bgRGB->r, $bgRGB->g, $bgRGB->b); + $text = $this->getAvatarText(); + $toReplace = ['{size}', '{fill}', '{letter}']; + return str_replace($toReplace, [$size, $bgHEX, $text], $this->svgTemplate); + } + + /** + * Generate png avatar from svg with Imagick + * + * @param int $size + * @return string|boolean + */ + protected function generateAvatarFromSvg(int $size) { + if (!extension_loaded('imagick')) { + return false; + } + try { + $font = __DIR__ . '/../../core/fonts/NotoSans-Regular.ttf'; + $svg = $this->getAvatarVector($size); + $avatar = new Imagick(); + $avatar->setFont($font); + $avatar->readImageBlob($svg); + $avatar->setImageFormat('png'); + $image = new OC_Image(); + $image->loadFromData($avatar); + return $image->data(); + } catch (\Exception $e) { + return false; + } + } + + /** + * Generate png avatar with GD + * + * @param string $userDisplayName + * @param int $size + * @return string + */ + protected function generateAvatar($userDisplayName, $size) { + $text = $this->getAvatarText(); + $backgroundColor = $this->avatarBackgroundColor($userDisplayName); + + $im = imagecreatetruecolor($size, $size); + $background = imagecolorallocate( + $im, + $backgroundColor->r, + $backgroundColor->g, + $backgroundColor->b + ); + $white = imagecolorallocate($im, 255, 255, 255); + imagefilledrectangle($im, 0, 0, $size, $size, $background); + + $font = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf'; + + $fontSize = $size * 0.4; + list($x, $y) = $this->imageTTFCenter( + $im, $text, $font, (int)$fontSize + ); + + imagettftext($im, $fontSize, 0, $x, $y, $white, $font, $text); + + ob_start(); + imagepng($im); + $data = ob_get_contents(); + ob_end_clean(); + + return $data; + } + + /** + * Calculate real image ttf center + * + * @param resource $image + * @param string $text text string + * @param string $font font path + * @param int $size font size + * @param int $angle + * @return array + */ + protected function imageTTFCenter( + $image, + string $text, + string $font, + int $size, + $angle = 0 + ): array { + // Image width & height + $xi = imagesx($image); + $yi = imagesy($image); + + // bounding box + $box = imagettfbbox($size, $angle, $font, $text); + + // imagettfbbox can return negative int + $xr = abs(max($box[2], $box[4])); + $yr = abs(max($box[5], $box[7])); + + // calculate bottom left placement + $x = intval(($xi - $xr) / 2); + $y = intval(($yi + $yr) / 2); + + return [$x, $y]; + } + + /** + * Calculate steps between two Colors + * @param object Color $steps start color + * @param object Color $ends end color + * @return array [r,g,b] steps for each color to go from $steps to $ends + */ + private function stepCalc($steps, $ends) { + $step = []; + $step[0] = ($ends[1]->r - $ends[0]->r) / $steps; + $step[1] = ($ends[1]->g - $ends[0]->g) / $steps; + $step[2] = ($ends[1]->b - $ends[0]->b) / $steps; + return $step; + } + + /** + * Convert a string to an integer evenly + * @param string $hash the text to parse + * @param int $maximum the maximum range + * @return int[] between 0 and $maximum + */ + private function mixPalette($steps, $color1, $color2) { + $palette = [$color1]; + $step = $this->stepCalc($steps, [$color1, $color2]); + for ($i = 1; $i < $steps; $i++) { + $r = intval($color1->r + ($step[0] * $i)); + $g = intval($color1->g + ($step[1] * $i)); + $b = intval($color1->b + ($step[2] * $i)); + $palette[] = new Color($r, $g, $b); + } + return $palette; + } + + /** + * Convert a string to an integer evenly + * @param string $hash the text to parse + * @param int $maximum the maximum range + * @return int between 0 and $maximum + */ + private function hashToInt($hash, $maximum) { + $final = 0; + $result = []; + + // Splitting evenly the string + for ($i = 0; $i < strlen($hash); $i++) { + // chars in md5 goes up to f, hex:16 + $result[] = intval(substr($hash, $i, 1), 16) % 16; + } + // Adds up all results + foreach ($result as $value) { + $final += $value; + } + // chars in md5 goes up to f, hex:16 + return intval($final % $maximum); + } + + /** + * @param string $hash + * @return Color Object containting r g b int in the range [0, 255] + */ + public function avatarBackgroundColor(string $hash) { + // Normalize hash + $hash = strtolower($hash); + + // Already a md5 hash? + if (preg_match('/^([0-9a-f]{4}-?){8}$/', $hash, $matches) !== 1) { + $hash = md5($hash); + } + + // Remove unwanted char + $hash = preg_replace('/[^0-9a-f]+/', '', $hash); + + $red = new Color(182, 70, 157); + $yellow = new Color(221, 203, 85); + $blue = new Color(0, 130, 201); // Nextcloud blue + + // Number of steps to go from a color to another + // 3 colors * 6 will result in 18 generated colors + $steps = 6; + + $palette1 = $this->mixPalette($steps, $red, $yellow); + $palette2 = $this->mixPalette($steps, $yellow, $blue); + $palette3 = $this->mixPalette($steps, $blue, $red); + + $finalPalette = array_merge($palette1, $palette2, $palette3); + + return $finalPalette[$this->hashToInt($hash, $steps * 3)]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Avatar/AvatarManager.php b/docker/overlays/nextcloud/html/lib/private/Avatar/AvatarManager.php new file mode 100644 index 0000000..5102396 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Avatar/AvatarManager.php @@ -0,0 +1,155 @@ + + * @author Christoph Wurst + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Weimann + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Avatar; + +use OC\User\Manager; +use OC\User\NoUserException; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IAvatar; +use OCP\IAvatarManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; + +/** + * This class implements methods to access Avatar functionality + */ +class AvatarManager implements IAvatarManager { + + /** @var Manager */ + private $userManager; + + /** @var IAppData */ + private $appData; + + /** @var IL10N */ + private $l; + + /** @var ILogger */ + private $logger; + + /** @var IConfig */ + private $config; + + /** + * AvatarManager constructor. + * + * @param Manager $userManager + * @param IAppData $appData + * @param IL10N $l + * @param ILogger $logger + * @param IConfig $config + */ + public function __construct( + Manager $userManager, + IAppData $appData, + IL10N $l, + ILogger $logger, + IConfig $config) { + $this->userManager = $userManager; + $this->appData = $appData; + $this->l = $l; + $this->logger = $logger; + $this->config = $config; + } + + /** + * return a user specific instance of \OCP\IAvatar + * @see \OCP\IAvatar + * @param string $userId the ownCloud user id + * @return \OCP\IAvatar + * @throws \Exception In case the username is potentially dangerous + * @throws NotFoundException In case there is no user folder yet + */ + public function getAvatar(string $userId) : IAvatar { + $user = $this->userManager->get($userId); + if ($user === null) { + throw new \Exception('user does not exist'); + } + + // sanitize userID - fixes casing issue (needed for the filesystem stuff that is done below) + $userId = $user->getUID(); + + try { + $folder = $this->appData->getFolder($userId); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder($userId); + } + + return new UserAvatar($folder, $this->l, $user, $this->logger, $this->config); + } + + /** + * Clear generated avatars + */ + public function clearCachedAvatars() { + $users = $this->config->getUsersForUserValue('avatar', 'generated', 'true'); + foreach ($users as $userId) { + try { + $folder = $this->appData->getFolder($userId); + $folder->delete(); + } catch (NotFoundException $e) { + $this->logger->debug("No cache for the user $userId. Ignoring..."); + } + $this->config->setUserValue($userId, 'avatar', 'generated', 'false'); + } + } + + public function deleteUserAvatar(string $userId): void { + try { + $folder = $this->appData->getFolder($userId); + $folder->delete(); + } catch (NotFoundException $e) { + $this->logger->debug("No cache for the user $userId. Ignoring avatar deletion"); + } catch (NotPermittedException $e) { + $this->logger->error("Unable to delete user avatars for $userId. gnoring avatar deletion"); + } catch (NoUserException $e) { + $this->logger->debug("User $userId not found. gnoring avatar deletion"); + } + $this->config->deleteUserValue($userId, 'avatar', 'generated'); + } + + /** + * Returns a GuestAvatar. + * + * @param string $name The guest name, e.g. "Albert". + * @return IAvatar + */ + public function getGuestAvatar(string $name): IAvatar { + return new GuestAvatar($name, $this->logger); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Avatar/GuestAvatar.php b/docker/overlays/nextcloud/html/lib/private/Avatar/GuestAvatar.php new file mode 100644 index 0000000..cc7e21b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Avatar/GuestAvatar.php @@ -0,0 +1,122 @@ + + * + * @author Michael Weimann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Avatar; + +use OCP\Files\SimpleFS\InMemoryFile; +use OCP\ILogger; + +/** + * This class represents a guest user's avatar. + */ +class GuestAvatar extends Avatar { + /** + * Holds the guest user display name. + * + * @var string + */ + private $userDisplayName; + + /** + * GuestAvatar constructor. + * + * @param string $userDisplayName The guest user display name + * @param ILogger $logger The logger + */ + public function __construct(string $userDisplayName, ILogger $logger) { + parent::__construct($logger); + $this->userDisplayName = $userDisplayName; + } + + /** + * Tests if the user has an avatar. + * + * @return true Guests always have an avatar. + */ + public function exists() { + return true; + } + + /** + * Returns the guest user display name. + * + * @return string + */ + public function getDisplayName(): string { + return $this->userDisplayName; + } + + /** + * Setting avatars isn't implemented for guests. + * + * @param \OCP\IImage|resource|string $data + * @return void + */ + public function set($data) { + // unimplemented for guest user avatars + } + + /** + * Removing avatars isn't implemented for guests. + */ + public function remove() { + // unimplemented for guest user avatars + } + + /** + * Generates an avatar for the guest. + * + * @param int $size The desired image size. + * @return InMemoryFile + */ + public function getFile($size) { + $avatar = $this->generateAvatar($this->userDisplayName, $size); + return new InMemoryFile('avatar.png', $avatar); + } + + /** + * Updates the display name if changed. + * + * @param string $feature The changed feature + * @param mixed $oldValue The previous value + * @param mixed $newValue The new value + * @return void + */ + public function userChanged($feature, $oldValue, $newValue) { + if ($feature === 'displayName') { + $this->userDisplayName = $newValue; + } + } + + /** + * Guests don't have custom avatars. + * + * @return bool + */ + public function isCustomAvatar(): bool { + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Avatar/UserAvatar.php b/docker/overlays/nextcloud/html/lib/private/Avatar/UserAvatar.php new file mode 100644 index 0000000..f7ace42 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Avatar/UserAvatar.php @@ -0,0 +1,337 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Michael Weimann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Avatar; + +use OC\NotSquareException; +use OC\User\User; +use OC_Image; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IConfig; +use OCP\IImage; +use OCP\IL10N; +use OCP\ILogger; + +/** + * This class represents a registered user's avatar. + */ +class UserAvatar extends Avatar { + /** @var IConfig */ + private $config; + + /** @var ISimpleFolder */ + private $folder; + + /** @var IL10N */ + private $l; + + /** @var User */ + private $user; + + /** + * UserAvatar constructor. + * + * @param IConfig $config The configuration + * @param ISimpleFolder $folder The avatar files folder + * @param IL10N $l The localization helper + * @param User $user The user this class manages the avatar for + * @param ILogger $logger The logger + */ + public function __construct( + ISimpleFolder $folder, + IL10N $l, + $user, + ILogger $logger, + IConfig $config) { + parent::__construct($logger); + $this->folder = $folder; + $this->l = $l; + $this->user = $user; + $this->config = $config; + } + + /** + * Check if an avatar exists for the user + * + * @return bool + */ + public function exists() { + return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png'); + } + + /** + * Sets the users avatar. + * + * @param IImage|resource|string $data An image object, imagedata or path to set a new avatar + * @throws \Exception if the provided file is not a jpg or png image + * @throws \Exception if the provided image is not valid + * @throws NotSquareException if the image is not square + * @return void + */ + public function set($data) { + $img = $this->getAvatarImage($data); + $data = $img->data(); + + $this->validateAvatar($img); + + $this->remove(true); + $type = $this->getAvatarImageType($img); + $file = $this->folder->newFile('avatar.' . $type); + $file->putContent($data); + + try { + $generated = $this->folder->getFile('generated'); + $generated->delete(); + } catch (NotFoundException $e) { + // + } + + $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'false'); + $this->user->triggerChange('avatar', $file); + } + + /** + * Returns an image from several sources. + * + * @param IImage|resource|string $data An image object, imagedata or path to the avatar + * @return IImage + */ + private function getAvatarImage($data) { + if ($data instanceof IImage) { + return $data; + } + + $img = new OC_Image(); + if (is_resource($data) && get_resource_type($data) === 'gd') { + $img->setResource($data); + } elseif (is_resource($data)) { + $img->loadFromFileHandle($data); + } else { + try { + // detect if it is a path or maybe the images as string + $result = @realpath($data); + if ($result === false || $result === null) { + $img->loadFromData($data); + } else { + $img->loadFromFile($data); + } + } catch (\Error $e) { + $img->loadFromData($data); + } + } + + return $img; + } + + /** + * Returns the avatar image type. + * + * @param IImage $avatar + * @return string + */ + private function getAvatarImageType(IImage $avatar) { + $type = substr($avatar->mimeType(), -3); + if ($type === 'peg') { + $type = 'jpg'; + } + return $type; + } + + /** + * Validates an avatar image: + * - must be "png" or "jpg" + * - must be "valid" + * - must be in square format + * + * @param IImage $avatar The avatar to validate + * @throws \Exception if the provided file is not a jpg or png image + * @throws \Exception if the provided image is not valid + * @throws NotSquareException if the image is not square + */ + private function validateAvatar(IImage $avatar) { + $type = $this->getAvatarImageType($avatar); + + if ($type !== 'jpg' && $type !== 'png') { + throw new \Exception($this->l->t('Unknown filetype')); + } + + if (!$avatar->valid()) { + throw new \Exception($this->l->t('Invalid image')); + } + + if (!($avatar->height() === $avatar->width())) { + throw new NotSquareException($this->l->t('Avatar image is not square')); + } + } + + /** + * Removes the users avatar. + * @return void + * @throws \OCP\Files\NotPermittedException + * @throws \OCP\PreConditionNotMetException + */ + public function remove(bool $silent = false) { + $avatars = $this->folder->getDirectoryListing(); + + $this->config->setUserValue($this->user->getUID(), 'avatar', 'version', + (int) $this->config->getUserValue($this->user->getUID(), 'avatar', 'version', 0) + 1); + + foreach ($avatars as $avatar) { + $avatar->delete(); + } + $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true'); + if (!$silent) { + $this->user->triggerChange('avatar', ''); + } + } + + /** + * Get the extension of the avatar. If there is no avatar throw Exception + * + * @return string + * @throws NotFoundException + */ + private function getExtension() { + if ($this->folder->fileExists('avatar.jpg')) { + return 'jpg'; + } elseif ($this->folder->fileExists('avatar.png')) { + return 'png'; + } + throw new NotFoundException; + } + + /** + * Returns the avatar for an user. + * + * If there is no avatar file yet, one is generated. + * + * @param int $size + * @return ISimpleFile + * @throws NotFoundException + * @throws \OCP\Files\NotPermittedException + * @throws \OCP\PreConditionNotMetException + */ + public function getFile($size) { + $size = (int) $size; + + try { + $ext = $this->getExtension(); + } catch (NotFoundException $e) { + if (!$data = $this->generateAvatarFromSvg(1024)) { + $data = $this->generateAvatar($this->getDisplayName(), 1024); + } + $avatar = $this->folder->newFile('avatar.png'); + $avatar->putContent($data); + $ext = 'png'; + + $this->folder->newFile('generated', ''); + $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true'); + } + + if ($size === -1) { + $path = 'avatar.' . $ext; + } else { + $path = 'avatar.' . $size . '.' . $ext; + } + + try { + $file = $this->folder->getFile($path); + } catch (NotFoundException $e) { + if ($size <= 0) { + throw new NotFoundException; + } + + if ($this->folder->fileExists('generated')) { + if (!$data = $this->generateAvatarFromSvg($size)) { + $data = $this->generateAvatar($this->getDisplayName(), $size); + } + } else { + $avatar = new OC_Image(); + $file = $this->folder->getFile('avatar.' . $ext); + $avatar->loadFromData($file->getContent()); + $avatar->resize($size); + $data = $avatar->data(); + } + + try { + $file = $this->folder->newFile($path); + $file->putContent($data); + } catch (NotPermittedException $e) { + $this->logger->error('Failed to save avatar for ' . $this->user->getUID()); + throw new NotFoundException(); + } + } + + if ($this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', null) === null) { + $generated = $this->folder->fileExists('generated') ? 'true' : 'false'; + $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', $generated); + } + + return $file; + } + + /** + * Returns the user display name. + * + * @return string + */ + public function getDisplayName(): string { + return $this->user->getDisplayName(); + } + + /** + * Handles user changes. + * + * @param string $feature The changed feature + * @param mixed $oldValue The previous value + * @param mixed $newValue The new value + * @throws NotPermittedException + * @throws \OCP\PreConditionNotMetException + */ + public function userChanged($feature, $oldValue, $newValue) { + // If the avatar is not generated (so an uploaded image) we skip this + if (!$this->folder->fileExists('generated')) { + return; + } + + $this->remove(); + } + + /** + * Check if the avatar of a user is a custom uploaded one + * + * @return bool + */ + public function isCustomAvatar(): bool { + return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Job.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Job.php new file mode 100644 index 0000000..312ec62 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Job.php @@ -0,0 +1,92 @@ + + * @author Morris Jobke + * @author Noveen Sachdeva + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob; + +use OCP\BackgroundJob\IJob; +use OCP\BackgroundJob\IJobList; +use OCP\ILogger; + +abstract class Job implements IJob { + /** @var int */ + protected $id; + + /** @var int */ + protected $lastRun; + + /** @var mixed */ + protected $argument; + + public function execute(IJobList $jobList, ILogger $logger = null) { + $jobList->setLastRun($this); + if ($logger === null) { + $logger = \OC::$server->getLogger(); + } + + try { + $jobStartTime = time(); + $logger->debug('Run ' . get_class($this) . ' job with ID ' . $this->getId(), ['app' => 'cron']); + $this->run($this->argument); + $timeTaken = time() - $jobStartTime; + + $logger->debug('Finished ' . get_class($this) . ' job with ID ' . $this->getId() . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']); + $jobList->setExecutionTime($this, $timeTaken); + } catch (\Throwable $e) { + if ($logger) { + $logger->logException($e, [ + 'app' => 'core', + 'message' => 'Error while running background job (class: ' . get_class($this) . ', arguments: ' . print_r($this->argument, true) . ')' + ]); + } + } + } + + abstract protected function run($argument); + + public function setId(int $id) { + $this->id = $id; + } + + public function setLastRun(int $lastRun) { + $this->lastRun = $lastRun; + } + + public function setArgument($argument) { + $this->argument = $argument; + } + + public function getId() { + return $this->id; + } + + public function getLastRun() { + return $this->lastRun; + } + + public function getArgument() { + return $this->argument; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/JobList.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/JobList.php new file mode 100644 index 0000000..37aecd6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/JobList.php @@ -0,0 +1,335 @@ + + * @author Georg Ehrke + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Noveen Sachdeva + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob; + +use OCP\AppFramework\QueryException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\AutoloadNotAllowedException; +use OCP\BackgroundJob\IJob; +use OCP\BackgroundJob\IJobList; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; + +class JobList implements IJobList { + + /** @var IDBConnection */ + protected $connection; + + /**@var IConfig */ + protected $config; + + /**@var ITimeFactory */ + protected $timeFactory; + + /** + * @param IDBConnection $connection + * @param IConfig $config + * @param ITimeFactory $timeFactory + */ + public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) { + $this->connection = $connection; + $this->config = $config; + $this->timeFactory = $timeFactory; + } + + /** + * @param IJob|string $job + * @param mixed $argument + */ + public function add($job, $argument = null) { + if (!$this->has($job, $argument)) { + if ($job instanceof IJob) { + $class = get_class($job); + } else { + $class = $job; + } + + $argument = json_encode($argument); + if (strlen($argument) > 4000) { + throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)'); + } + + $query = $this->connection->getQueryBuilder(); + $query->insert('jobs') + ->values([ + 'class' => $query->createNamedParameter($class), + 'argument' => $query->createNamedParameter($argument), + 'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT), + 'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT), + ]); + $query->execute(); + } + } + + /** + * @param IJob|string $job + * @param mixed $argument + */ + public function remove($job, $argument = null) { + if ($job instanceof IJob) { + $class = get_class($job); + } else { + $class = $job; + } + + $query = $this->connection->getQueryBuilder(); + $query->delete('jobs') + ->where($query->expr()->eq('class', $query->createNamedParameter($class))); + if (!is_null($argument)) { + $argument = json_encode($argument); + $query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument))); + } + $query->execute(); + } + + /** + * @param int $id + */ + protected function removeById($id) { + $query = $this->connection->getQueryBuilder(); + $query->delete('jobs') + ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + /** + * check if a job is in the list + * + * @param IJob|string $job + * @param mixed $argument + * @return bool + */ + public function has($job, $argument) { + if ($job instanceof IJob) { + $class = get_class($job); + } else { + $class = $job; + } + $argument = json_encode($argument); + + $query = $this->connection->getQueryBuilder(); + $query->select('id') + ->from('jobs') + ->where($query->expr()->eq('class', $query->createNamedParameter($class))) + ->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument))) + ->setMaxResults(1); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + return (bool) $row; + } + + /** + * get all jobs in the list + * + * @return IJob[] + * @deprecated 9.0.0 - This method is dangerous since it can cause load and + * memory problems when creating too many instances. + */ + public function getAll() { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs'); + $result = $query->execute(); + + $jobs = []; + while ($row = $result->fetch()) { + $job = $this->buildJob($row); + if ($job) { + $jobs[] = $job; + } + } + $result->closeCursor(); + + return $jobs; + } + + /** + * get the next job in the list + * + * @return IJob|null + */ + public function getNext() { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))) + ->orderBy('last_checked', 'ASC') + ->setMaxResults(1); + + $update = $this->connection->getQueryBuilder(); + $update->update('jobs') + ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime())) + ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime())) + ->where($update->expr()->eq('id', $update->createParameter('jobid'))) + ->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at'))) + ->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked'))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $update->setParameter('jobid', $row['id']); + $update->setParameter('reserved_at', $row['reserved_at']); + $update->setParameter('last_checked', $row['last_checked']); + $count = $update->execute(); + + if ($count === 0) { + // Background job already executed elsewhere, try again. + return $this->getNext(); + } + $job = $this->buildJob($row); + + if ($job === null) { + // set the last_checked to 12h in the future to not check failing jobs all over again + $reset = $this->connection->getQueryBuilder(); + $reset->update('jobs') + ->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT)) + ->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT)) + ->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT))); + $reset->execute(); + + // Background job from disabled app, try again. + return $this->getNext(); + } + + return $job; + } else { + return null; + } + } + + /** + * @param int $id + * @return IJob|null + */ + public function getById($id) { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + return $this->buildJob($row); + } else { + return null; + } + } + + /** + * get the job object from a row in the db + * + * @param array $row + * @return IJob|null + */ + private function buildJob($row) { + try { + try { + // Try to load the job as a service + /** @var IJob $job */ + $job = \OC::$server->query($row['class']); + } catch (QueryException $e) { + if (class_exists($row['class'])) { + $class = $row['class']; + $job = new $class(); + } else { + // job from disabled app or old version of an app, no need to do anything + return null; + } + } + + $job->setId((int) $row['id']); + $job->setLastRun((int) $row['last_run']); + $job->setArgument(json_decode($row['argument'], true)); + return $job; + } catch (AutoloadNotAllowedException $e) { + // job is from a disabled app, ignore + return null; + } + } + + /** + * set the job that was last ran + * + * @param IJob $job + */ + public function setLastJob(IJob $job) { + $this->unlockJob($job); + $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId()); + } + + /** + * Remove the reservation for a job + * + * @param IJob $job + */ + public function unlockJob(IJob $job) { + $query = $this->connection->getQueryBuilder(); + $query->update('jobs') + ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + /** + * set the lastRun of $job to now + * + * @param IJob $job + */ + public function setLastRun(IJob $job) { + $query = $this->connection->getQueryBuilder(); + $query->update('jobs') + ->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + /** + * @param IJob $job + * @param $timeTaken + */ + public function setExecutionTime(IJob $job, $timeTaken) { + $query = $this->connection->getQueryBuilder(); + $query->update('jobs') + ->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/QueuedJob.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/QueuedJob.php new file mode 100644 index 0000000..9adaeff --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/QueuedJob.php @@ -0,0 +1,36 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob\Legacy; + +class QueuedJob extends \OC\BackgroundJob\QueuedJob { + public function run($argument) { + $class = $argument['klass']; + $method = $argument['method']; + $parameters = $argument['parameters']; + if (is_callable([$class, $method])) { + call_user_func([$class, $method], $parameters); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/RegularJob.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/RegularJob.php new file mode 100644 index 0000000..95ad8a4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/Legacy/RegularJob.php @@ -0,0 +1,39 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob\Legacy; + +use OCP\AutoloadNotAllowedException; + +class RegularJob extends \OC\BackgroundJob\Job { + public function run($argument) { + try { + if (is_callable($argument)) { + call_user_func($argument); + } + } catch (AutoloadNotAllowedException $e) { + // job is from a disabled app, ignore + return null; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/QueuedJob.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/QueuedJob.php new file mode 100644 index 0000000..6f10ee4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/QueuedJob.php @@ -0,0 +1,48 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob; + +use OCP\ILogger; + +/** + * Class QueuedJob + * + * create a background job that is to be executed once + * + * @package OC\BackgroundJob + */ +abstract class QueuedJob extends Job { + /** + * run the job, then remove it from the joblist + * + * @param JobList $jobList + * @param ILogger|null $logger + */ + public function execute($jobList, ILogger $logger = null) { + $jobList->remove($this, $this->argument); + parent::execute($jobList, $logger); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/BackgroundJob/TimedJob.php b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/TimedJob.php new file mode 100644 index 0000000..c0ce130 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/BackgroundJob/TimedJob.php @@ -0,0 +1,62 @@ + + * @author Daniel Kesselberg + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\BackgroundJob; + +use OCP\BackgroundJob\IJobList; +use OCP\ILogger; + +/** + * Class QueuedJob + * + * create a background job that is to be executed at an interval + * + * @package OC\BackgroundJob + */ +abstract class TimedJob extends Job { + protected $interval = 0; + + /** + * set the interval for the job + * + * @param int $interval + */ + public function setInterval($interval) { + $this->interval = $interval; + } + + /** + * run the job if + * + * @param IJobList $jobList + * @param ILogger|null $logger + */ + public function execute($jobList, ILogger $logger = null) { + if ((time() - $this->lastRun) > $this->interval) { + parent::execute($jobList, $logger); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Broadcast/Events/BroadcastEvent.php b/docker/overlays/nextcloud/html/lib/private/Broadcast/Events/BroadcastEvent.php new file mode 100644 index 0000000..7d4d701 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Broadcast/Events/BroadcastEvent.php @@ -0,0 +1,60 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Broadcast\Events; + +use JsonSerializable; +use OCP\Broadcast\Events\IBroadcastEvent; +use OCP\EventDispatcher\ABroadcastedEvent; +use OCP\EventDispatcher\Event; + +class BroadcastEvent extends Event implements IBroadcastEvent { + + /** @var ABroadcastedEvent */ + private $event; + + public function __construct(ABroadcastedEvent $event) { + parent::__construct(); + + $this->event = $event; + } + + public function getName(): string { + return $this->event->broadcastAs(); + } + + public function getUids(): array { + return $this->event->getUids(); + } + + public function getPayload(): JsonSerializable { + return $this->event; + } + + public function setBroadcasted(): void { + $this->event->setBroadcasted(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Cache/CappedMemoryCache.php b/docker/overlays/nextcloud/html/lib/private/Cache/CappedMemoryCache.php new file mode 100644 index 0000000..28e0875 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Cache/CappedMemoryCache.php @@ -0,0 +1,95 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Cache; + +use OCP\ICache; + +/** + * In-memory cache with a capacity limit to keep memory usage in check + * + * Uses a simple FIFO expiry mechanism + */ +class CappedMemoryCache implements ICache, \ArrayAccess { + private $capacity; + private $cache = []; + + public function __construct($capacity = 512) { + $this->capacity = $capacity; + } + + public function hasKey($key) { + return isset($this->cache[$key]); + } + + public function get($key) { + return isset($this->cache[$key]) ? $this->cache[$key] : null; + } + + public function set($key, $value, $ttl = 0) { + if (is_null($key)) { + $this->cache[] = $value; + } else { + $this->cache[$key] = $value; + } + $this->garbageCollect(); + } + + public function remove($key) { + unset($this->cache[$key]); + return true; + } + + public function clear($prefix = '') { + $this->cache = []; + return true; + } + + public function offsetExists($offset) { + return $this->hasKey($offset); + } + + public function &offsetGet($offset) { + return $this->cache[$offset]; + } + + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + public function offsetUnset($offset) { + $this->remove($offset); + } + + public function getData() { + return $this->cache; + } + + + private function garbageCollect() { + while (count($this->cache) > $this->capacity) { + reset($this->cache); + $key = key($this->cache); + $this->remove($key); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Cache/File.php b/docker/overlays/nextcloud/html/lib/private/Cache/File.php new file mode 100644 index 0000000..be6540f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Cache/File.php @@ -0,0 +1,207 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sebastian Wessalowski + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Cache; + +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\ICache; +use OCP\ILogger; +use OCP\Security\ISecureRandom; + +class File implements ICache { + + /** @var View */ + protected $storage; + + /** + * Returns the cache storage for the logged in user + * + * @return \OC\Files\View cache storage + * @throws \OC\ForbiddenException + * @throws \OC\User\NoUserException + */ + protected function getStorage() { + if (isset($this->storage)) { + return $this->storage; + } + if (\OC::$server->getUserSession()->isLoggedIn()) { + $rootView = new View(); + $user = \OC::$server->getUserSession()->getUser(); + Filesystem::initMountPoints($user->getUID()); + if (!$rootView->file_exists('/' . $user->getUID() . '/cache')) { + $rootView->mkdir('/' . $user->getUID() . '/cache'); + } + $this->storage = new View('/' . $user->getUID() . '/cache'); + return $this->storage; + } else { + \OCP\Util::writeLog('core', 'Can\'t get cache storage, user not logged in', ILogger::ERROR); + throw new \OC\ForbiddenException('Can\t get cache storage, user not logged in'); + } + } + + /** + * @param string $key + * @return mixed|null + * @throws \OC\ForbiddenException + */ + public function get($key) { + $result = null; + if ($this->hasKey($key)) { + $storage = $this->getStorage(); + $result = $storage->file_get_contents($key); + } + return $result; + } + + /** + * Returns the size of the stored/cached data + * + * @param string $key + * @return int + */ + public function size($key) { + $result = 0; + if ($this->hasKey($key)) { + $storage = $this->getStorage(); + $result = $storage->filesize($key); + } + return $result; + } + + /** + * @param string $key + * @param mixed $value + * @param int $ttl + * @return bool|mixed + * @throws \OC\ForbiddenException + */ + public function set($key, $value, $ttl = 0) { + $storage = $this->getStorage(); + $result = false; + // unique id to avoid chunk collision, just in case + $uniqueId = \OC::$server->getSecureRandom()->generate( + 16, + ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER + ); + + // use part file to prevent hasKey() to find the key + // while it is being written + $keyPart = $key . '.' . $uniqueId . '.part'; + if ($storage and $storage->file_put_contents($keyPart, $value)) { + if ($ttl === 0) { + $ttl = 86400; // 60*60*24 + } + $result = $storage->touch($keyPart, time() + $ttl); + $result &= $storage->rename($keyPart, $key); + } + return $result; + } + + /** + * @param string $key + * @return bool + * @throws \OC\ForbiddenException + */ + public function hasKey($key) { + $storage = $this->getStorage(); + if ($storage && $storage->is_file($key) && $storage->isReadable($key)) { + return true; + } + return false; + } + + /** + * @param string $key + * @return bool|mixed + * @throws \OC\ForbiddenException + */ + public function remove($key) { + $storage = $this->getStorage(); + if (!$storage) { + return false; + } + return $storage->unlink($key); + } + + /** + * @param string $prefix + * @return bool + * @throws \OC\ForbiddenException + */ + public function clear($prefix = '') { + $storage = $this->getStorage(); + if ($storage and $storage->is_dir('/')) { + $dh = $storage->opendir('/'); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if ($file != '.' and $file != '..' and ($prefix === '' || strpos($file, $prefix) === 0)) { + $storage->unlink('/' . $file); + } + } + } + } + return true; + } + + /** + * Runs GC + * @throws \OC\ForbiddenException + */ + public function gc() { + $storage = $this->getStorage(); + if ($storage) { + // extra hour safety, in case of stray part chunks that take longer to write, + // because touch() is only called after the chunk was finished + $now = time() - 3600; + $dh = $storage->opendir('/'); + if (!is_resource($dh)) { + return null; + } + while (($file = readdir($dh)) !== false) { + if ($file != '.' and $file != '..') { + try { + $mtime = $storage->filemtime('/' . $file); + if ($mtime < $now) { + $storage->unlink('/' . $file); + } + } catch (\OCP\Lock\LockedException $e) { + // ignore locked chunks + \OC::$server->getLogger()->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']); + } catch (\OCP\Files\ForbiddenException $e) { + \OC::$server->getLogger()->debug('Could not cleanup forbidden chunk "' . $file . '"', ['app' => 'core']); + } catch (\OCP\Files\LockNotAcquiredException $e) { + \OC::$server->getLogger()->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']); + } + } + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Calendar/Manager.php b/docker/overlays/nextcloud/html/lib/private/Calendar/Manager.php new file mode 100644 index 0000000..ab8af87 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Calendar/Manager.php @@ -0,0 +1,141 @@ + + * + * @author Christoph Wurst + * @author Georg Ehrke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Calendar; + +use OCP\Calendar\ICalendar; + +class Manager implements \OCP\Calendar\IManager { + + /** + * @var ICalendar[] holds all registered calendars + */ + private $calendars=[]; + + /** + * @var \Closure[] to call to load/register calendar providers + */ + private $calendarLoaders=[]; + + /** + * This function is used to search and find objects within the user's calendars. + * In case $pattern is empty all events/journals/todos will be returned. + * + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - optional parameters: + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * @param integer|null $limit - limit number of search results + * @param integer|null $offset - offset for paging of search results + * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs + * @since 13.0.0 + */ + public function search($pattern, array $searchProperties=[], array $options=[], $limit=null, $offset=null) { + $this->loadCalendars(); + $result = []; + foreach ($this->calendars as $calendar) { + $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset); + foreach ($r as $o) { + $o['calendar-key'] = $calendar->getKey(); + $result[] = $o; + } + } + + return $result; + } + + /** + * Check if calendars are available + * + * @return bool true if enabled, false if not + * @since 13.0.0 + */ + public function isEnabled() { + return !empty($this->calendars) || !empty($this->calendarLoaders); + } + + /** + * Registers a calendar + * + * @param ICalendar $calendar + * @return void + * @since 13.0.0 + */ + public function registerCalendar(ICalendar $calendar) { + $this->calendars[$calendar->getKey()] = $calendar; + } + + /** + * Unregisters a calendar + * + * @param ICalendar $calendar + * @return void + * @since 13.0.0 + */ + public function unregisterCalendar(ICalendar $calendar) { + unset($this->calendars[$calendar->getKey()]); + } + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * calendars are actually requested + * + * @param \Closure $callable + * @return void + * @since 13.0.0 + */ + public function register(\Closure $callable) { + $this->calendarLoaders[] = $callable; + } + + /** + * @return ICalendar[] + * @since 13.0.0 + */ + public function getCalendars() { + $this->loadCalendars(); + + return array_values($this->calendars); + } + + /** + * removes all registered calendar instances + * @return void + * @since 13.0.0 + */ + public function clear() { + $this->calendars = []; + $this->calendarLoaders = []; + } + + /** + * loads all calendars + */ + private function loadCalendars() { + foreach ($this->calendarLoaders as $callable) { + $callable($this); + } + $this->calendarLoaders = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Calendar/Resource/Manager.php b/docker/overlays/nextcloud/html/lib/private/Calendar/Resource/Manager.php new file mode 100644 index 0000000..2c63ce9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Calendar/Resource/Manager.php @@ -0,0 +1,115 @@ + + * + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Calendar\Resource; + +use OCP\Calendar\Resource\IBackend; +use OCP\IServerContainer; + +class Manager implements \OCP\Calendar\Resource\IManager { + + /** @var IServerContainer */ + private $server; + + /** @var string[] holds all registered resource backends */ + private $backends = []; + + /** @var IBackend[] holds all backends that have been initialized already */ + private $initializedBackends = []; + + /** + * Manager constructor. + * + * @param IServerContainer $server + */ + public function __construct(IServerContainer $server) { + $this->server = $server; + } + + /** + * Registers a resource backend + * + * @param string $backendClass + * @return void + * @since 14.0.0 + */ + public function registerBackend(string $backendClass) { + $this->backends[$backendClass] = $backendClass; + } + + /** + * Unregisters a resource backend + * + * @param string $backendClass + * @return void + * @since 14.0.0 + */ + public function unregisterBackend(string $backendClass) { + unset($this->backends[$backendClass], $this->initializedBackends[$backendClass]); + } + + /** + * @return IBackend[] + * @throws \OCP\AppFramework\QueryException + * @since 14.0.0 + */ + public function getBackends():array { + foreach ($this->backends as $backend) { + if (isset($this->initializedBackends[$backend])) { + continue; + } + + $this->initializedBackends[$backend] = $this->server->query($backend); + } + + return array_values($this->initializedBackends); + } + + /** + * @param string $backendId + * @throws \OCP\AppFramework\QueryException + * @return IBackend|null + */ + public function getBackend($backendId) { + $backends = $this->getBackends(); + foreach ($backends as $backend) { + if ($backend->getBackendIdentifier() === $backendId) { + return $backend; + } + } + + return null; + } + + /** + * removes all registered backend instances + * @return void + * @since 14.0.0 + */ + public function clear() { + $this->backends = []; + $this->initializedBackends = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Calendar/Room/Manager.php b/docker/overlays/nextcloud/html/lib/private/Calendar/Room/Manager.php new file mode 100644 index 0000000..5001b71 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Calendar/Room/Manager.php @@ -0,0 +1,115 @@ + + * + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Calendar\Room; + +use OCP\Calendar\Room\IBackend; +use OCP\IServerContainer; + +class Manager implements \OCP\Calendar\Room\IManager { + + /** @var IServerContainer */ + private $server; + + /** @var string[] holds all registered resource backends */ + private $backends = []; + + /** @var IBackend[] holds all backends that have been initialized already */ + private $initializedBackends = []; + + /** + * Manager constructor. + * + * @param IServerContainer $server + */ + public function __construct(IServerContainer $server) { + $this->server = $server; + } + + /** + * Registers a resource backend + * + * @param string $backendClass + * @return void + * @since 14.0.0 + */ + public function registerBackend(string $backendClass) { + $this->backends[$backendClass] = $backendClass; + } + + /** + * Unregisters a resource backend + * + * @param string $backendClass + * @return void + * @since 14.0.0 + */ + public function unregisterBackend(string $backendClass) { + unset($this->backends[$backendClass], $this->initializedBackends[$backendClass]); + } + + /** + * @return IBackend[] + * @throws \OCP\AppFramework\QueryException + * @since 14.0.0 + */ + public function getBackends():array { + foreach ($this->backends as $backend) { + if (isset($this->initializedBackends[$backend])) { + continue; + } + + $this->initializedBackends[$backend] = $this->server->query($backend); + } + + return array_values($this->initializedBackends); + } + + /** + * @param string $backendId + * @throws \OCP\AppFramework\QueryException + * @return IBackend|null + */ + public function getBackend($backendId) { + $backends = $this->getBackends(); + foreach ($backends as $backend) { + if ($backend->getBackendIdentifier() === $backendId) { + return $backend; + } + } + + return null; + } + + /** + * removes all registered backend instances + * @return void + * @since 14.0.0 + */ + public function clear() { + $this->backends = []; + $this->initializedBackends = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/CapabilitiesManager.php b/docker/overlays/nextcloud/html/lib/private/CapabilitiesManager.php new file mode 100644 index 0000000..6a4d97a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/CapabilitiesManager.php @@ -0,0 +1,93 @@ + + * @author Christoph Wurst + * @author Julius Härtl + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\AppFramework\QueryException; +use OCP\Capabilities\ICapability; +use OCP\Capabilities\IPublicCapability; +use OCP\ILogger; + +class CapabilitiesManager { + + /** @var \Closure[] */ + private $capabilities = []; + + /** @var ILogger */ + private $logger; + + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + /** + * Get an array of al the capabilities that are registered at this manager + * + * @param bool $public get public capabilities only + * @throws \InvalidArgumentException + * @return array + */ + public function getCapabilities(bool $public = false) : array { + $capabilities = []; + foreach ($this->capabilities as $capability) { + try { + $c = $capability(); + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'message' => 'CapabilitiesManager', + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + continue; + } + + if ($c instanceof ICapability) { + if (!$public || $c instanceof IPublicCapability) { + $capabilities = array_replace_recursive($capabilities, $c->getCapabilities()); + } + } else { + throw new \InvalidArgumentException('The given Capability (' . get_class($c) . ') does not implement the ICapability interface'); + } + } + + return $capabilities; + } + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * capabilities are actually requested + * + * $callable has to return an instance of OCP\Capabilities\ICapability + * + * @param \Closure $callable + */ + public function registerCapability(\Closure $callable) { + $this->capabilities[] = $callable; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/AutoComplete/Manager.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/AutoComplete/Manager.php new file mode 100644 index 0000000..dc3b76f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/AutoComplete/Manager.php @@ -0,0 +1,82 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\AutoComplete; + +use OCP\Collaboration\AutoComplete\IManager; +use OCP\Collaboration\AutoComplete\ISorter; +use OCP\IServerContainer; + +class Manager implements IManager { + /** @var string[] */ + protected $sorters =[]; + + /** @var ISorter[] */ + protected $sorterInstances = []; + /** @var IServerContainer */ + private $c; + + public function __construct(IServerContainer $container) { + $this->c = $container; + } + + public function runSorters(array $sorters, array &$sortArray, array $context) { + $sorterInstances = $this->getSorters(); + while ($sorter = array_shift($sorters)) { + if (isset($sorterInstances[$sorter])) { + $sorterInstances[$sorter]->sort($sortArray, $context); + } else { + $this->c->getLogger()->warning('No sorter for ID "{id}", skipping', [ + 'app' => 'core', 'id' => $sorter + ]); + } + } + } + + public function registerSorter($className) { + $this->sorters[] = $className; + } + + protected function getSorters() { + if (count($this->sorterInstances) === 0) { + foreach ($this->sorters as $sorter) { + /** @var ISorter $instance */ + $instance = $this->c->resolve($sorter); + if (!$instance instanceof ISorter) { + $this->c->getLogger()->notice('Skipping sorter which is not an instance of ISorter. Class name: {class}', + ['app' => 'core', 'class' => $sorter]); + continue; + } + $sorterId = trim($instance->getId()); + if (trim($sorterId) === '') { + $this->c->getLogger()->notice('Skipping sorter with empty ID. Class name: {class}', + ['app' => 'core', 'class' => $sorter]); + continue; + } + $this->sorterInstances[$sorterId] = $instance; + } + } + return $this->sorterInstances; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/GroupPlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/GroupPlugin.php new file mode 100644 index 0000000..f65763a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -0,0 +1,141 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUserSession; +use OCP\Share\IShare; + +class GroupPlugin implements ISearchPlugin { + protected $shareeEnumeration; + protected $shareWithGroupOnly; + + /** @var IGroupManager */ + private $groupManager; + /** @var IConfig */ + private $config; + /** @var IUserSession */ + private $userSession; + + public function __construct(IConfig $config, IGroupManager $groupManager, IUserSession $userSession) { + $this->groupManager = $groupManager; + $this->config = $config; + $this->userSession = $userSession; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $hasMoreResults = false; + $result = ['wide' => [], 'exact' => []]; + + $groups = $this->groupManager->search($search, $limit, $offset); + $groupIds = array_map(function (IGroup $group) { + return $group->getGID(); + }, $groups); + + if (!$this->shareeEnumeration || count($groups) < $limit) { + $hasMoreResults = true; + } + + $userGroups = []; + if (!empty($groups) && ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly)) { + // Intersect all the groups that match with the groups this user is a member of + $userGroups = $this->groupManager->getUserGroups($this->userSession->getUser()); + $userGroups = array_map(function (IGroup $group) { + return $group->getGID(); + }, $userGroups); + $groupIds = array_intersect($groupIds, $userGroups); + } + + $lowerSearch = strtolower($search); + foreach ($groups as $group) { + if ($group->hideFromCollaboration()) { + continue; + } + + // FIXME: use a more efficient approach + $gid = $group->getGID(); + if (!in_array($gid, $groupIds)) { + continue; + } + if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) { + $result['exact'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => IShare::TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } else { + if ($this->shareeEnumerationInGroupOnly && !in_array($group->getGID(), $userGroups, true)) { + continue; + } + $result['wide'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => IShare::TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } + } + + if ($offset === 0 && empty($result['exact'])) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $group = $this->groupManager->get($search); + if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) { + $result['exact'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => IShare::TYPE_GROUP, + 'shareWith' => $group->getGID(), + ], + ]; + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + $type = new SearchResultType('groups'); + $searchResult->addResultSet($type, $result['wide'], $result['exact']); + + return $hasMoreResults; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/LookupPlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/LookupPlugin.php new file mode 100644 index 0000000..974254e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -0,0 +1,125 @@ + + * + * @author Arthur Schiwon + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Federation\ICloudIdManager; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUserSession; +use OCP\Share\IShare; + +class LookupPlugin implements ISearchPlugin { + + /** @var IConfig */ + private $config; + /** @var IClientService */ + private $clientService; + /** @var string remote part of the current user's cloud id */ + private $currentUserRemote; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var ILogger */ + private $logger; + + public function __construct(IConfig $config, + IClientService $clientService, + IUserSession $userSession, + ICloudIdManager $cloudIdManager, + ILogger $logger) { + $this->config = $config; + $this->clientService = $clientService; + $this->cloudIdManager = $cloudIdManager; + $currentUserCloudId = $userSession->getUser()->getCloudId(); + $this->currentUserRemote = $cloudIdManager->resolveCloudId($currentUserCloudId)->getRemote(); + $this->logger = $logger; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $isGlobalScaleEnabled = $this->config->getSystemValue('gs.enabled', false); + $isLookupServerEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes'; + $hasInternetConnection = (bool)$this->config->getSystemValue('has_internet_connection', true); + + // if case of Global Scale we always search the lookup server + if (!$isGlobalScaleEnabled && (!$isLookupServerEnabled || !$hasInternetConnection)) { + return false; + } + + $lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com'); + if (empty($lookupServerUrl)) { + return false; + } + $lookupServerUrl = rtrim($lookupServerUrl, '/'); + $result = []; + + try { + $client = $this->clientService->newClient(); + $response = $client->get( + $lookupServerUrl . '/users?search=' . urlencode($search), + [ + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + + $body = json_decode($response->getBody(), true); + + foreach ($body as $lookup) { + try { + $remote = $this->cloudIdManager->resolveCloudId($lookup['federationId'])->getRemote(); + } catch (\Exception $e) { + $this->logger->error('Can not parse federated cloud ID "' . $lookup['federationId'] . '"'); + $this->logger->error($e->getMessage()); + continue; + } + if ($this->currentUserRemote === $remote) { + continue; + } + $name = isset($lookup['name']['value']) ? $lookup['name']['value'] : ''; + $label = empty($name) ? $lookup['federationId'] : $name . ' (' . $lookup['federationId'] . ')'; + $result[] = [ + 'label' => $label, + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $lookup['federationId'], + ], + 'extra' => $lookup, + ]; + } + } catch (\Exception $e) { + } + + $type = new SearchResultType('lookup'); + $searchResult->addResultSet($type, $result, []); + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/MailPlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/MailPlugin.php new file mode 100644 index 0000000..90dc591 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -0,0 +1,247 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Tobia De Koninck + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudId; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Share\IShare; + +class MailPlugin implements ISearchPlugin { + protected $shareeEnumeration; + protected $shareWithGroupOnly; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IUserSession */ + private $userSession; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + } + + /** + * @param $search + * @param $limit + * @param $offset + * @param ISearchResult $searchResult + * @return bool + * @since 13.0.0 + */ + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = $userResults = ['wide' => [], 'exact' => []]; + $userType = new SearchResultType('users'); + $emailType = new SearchResultType('emails'); + + // Search in contacts + $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN'], ['limit' => $limit, 'offset' => $offset]); + $lowerSearch = strtolower($search); + foreach ($addressBookContacts as $contact) { + if (isset($contact['EMAIL'])) { + $emailAddresses = $contact['EMAIL']; + if (\is_string($emailAddresses)) { + $emailAddresses = [$emailAddresses]; + } + foreach ($emailAddresses as $type => $emailAddress) { + $displayName = $emailAddress; + $emailAddressType = null; + if (\is_array($emailAddress)) { + $emailAddressData = $emailAddress; + $emailAddress = $emailAddressData['value']; + $emailAddressType = $emailAddressData['type']; + } + if (isset($contact['FN'])) { + $displayName = $contact['FN'] . ' (' . $emailAddress . ')'; + } + $exactEmailMatch = strtolower($emailAddress) === $lowerSearch; + + if (isset($contact['isLocalSystemBook'])) { + if ($this->shareWithGroupOnly) { + /* + * Check if the user may share with the user associated with the e-mail of the just found contact + */ + $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + $found = false; + foreach ($userGroups as $userGroup) { + if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) { + $found = true; + break; + } + } + if (!$found) { + continue; + } + } + if ($exactEmailMatch) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { + $singleResult = [[ + 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $cloud->getUser(), + ], + ]]; + $searchResult->addResultSet($userType, [], $singleResult); + $searchResult->markExactIdMatch($emailType); + } + return false; + } + + if ($this->shareeEnumeration) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + $addToWide = !$this->shareeEnumerationInGroupOnly; + if ($this->shareeEnumerationInGroupOnly) { + $addToWide = false; + $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + foreach ($userGroups as $userGroup) { + if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) { + $addToWide = true; + break; + } + } + } + if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { + $userResults['wide'][] = [ + 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $cloud->getUser(), + ], + ]; + continue; + } + } + continue; + } + + if ($exactEmailMatch + || isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch) { + if ($exactEmailMatch) { + $searchResult->markExactIdMatch($emailType); + } + $result['exact'][] = [ + 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', + 'value' => [ + 'shareType' => IShare::TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', + 'value' => [ + 'shareType' => IShare::TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } + } + } + } + + $reachedEnd = true; + if (!$this->shareeEnumeration) { + $result['wide'] = []; + $userResults['wide'] = []; + } else { + $reachedEnd = (count($result['wide']) < $offset + $limit) && + (count($userResults['wide']) < $offset + $limit); + + $result['wide'] = array_slice($result['wide'], $offset, $limit); + $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit); + } + + + if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) { + $result['exact'][] = [ + 'label' => $search, + 'uuid' => $search, + 'value' => [ + 'shareType' => IShare::TYPE_EMAIL, + 'shareWith' => $search, + ], + ]; + } + + if (!empty($userResults['wide'])) { + $searchResult->addResultSet($userType, $userResults['wide'], []); + } + $searchResult->addResultSet($emailType, $result['wide'], $result['exact']); + + return !$reachedEnd; + } + + public function isCurrentUser(ICloudId $cloud): bool { + $currentUser = $this->userSession->getUser(); + return $currentUser instanceof IUser ? $currentUser->getUID() === $cloud->getUser() : false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php new file mode 100644 index 0000000..62e0a43 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php @@ -0,0 +1,95 @@ + + * + * @author Bjoern Schiessle + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Federation\ICloudFederationProviderManager; +use OCP\Federation\ICloudIdManager; +use OCP\Share; +use OCP\Share\IShare; + +class RemoteGroupPlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var bool */ + private $enabled = false; + + public function __construct(ICloudFederationProviderManager $cloudFederationProviderManager, ICloudIdManager $cloudIdManager) { + try { + $fileSharingProvider = $cloudFederationProviderManager->getCloudFederationProvider('file'); + $supportedShareTypes = $fileSharingProvider->getSupportedShareTypes(); + if (in_array('group', $supportedShareTypes)) { + $this->enabled = true; + } + } catch (\Exception $e) { + // do nothing, just don't enable federated group shares + } + $this->cloudIdManager = $cloudIdManager; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $resultType = new SearchResultType('remote_groups'); + + if ($this->enabled && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { + list($remoteGroup, $serverUrl) = $this->splitGroupRemote($search); + $result['exact'][] = [ + 'label' => $remoteGroup . " ($serverUrl)", + 'guid' => $remoteGroup, + 'name' => $remoteGroup, + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE_GROUP, + 'shareWith' => $search, + 'server' => $serverUrl, + ], + ]; + } + + $searchResult->addResultSet($resultType, $result['wide'], $result['exact']); + + return true; + } + + /** + * split group and remote from federated cloud id + * + * @param string $address federated share address + * @return array [user, remoteURL] + * @throws \InvalidArgumentException + */ + public function splitGroupRemote($address) { + try { + $cloudId = $this->cloudIdManager->resolveCloudId($address); + return [$cloudId->getUser(), $cloudId->getRemote()]; + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemotePlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemotePlugin.php new file mode 100644 index 0000000..af94027 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -0,0 +1,191 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\IShare; + +class RemotePlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + /** @var IUserManager */ + private $userManager; + /** @var string */ + private $userId = ''; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + $this->userManager = $userManager; + $user = $userSession->getUser(); + if ($user !== null) { + $this->userId = $user->getUID(); + } + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $resultType = new SearchResultType('remotes'); + + // Search in contacts + $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], ['limit' => $limit, 'offset' => $offset]); + foreach ($addressBookContacts as $contact) { + if (isset($contact['isLocalSystemBook'])) { + continue; + } + if (isset($contact['CLOUD'])) { + $cloudIds = $contact['CLOUD']; + if (is_string($cloudIds)) { + $cloudIds = [$cloudIds]; + } + $lowerSearch = strtolower($search); + foreach ($cloudIds as $cloudId) { + $cloudIdType = ''; + if (\is_array($cloudId)) { + $cloudIdData = $cloudId; + $cloudId = $cloudIdData['value']; + $cloudIdType = $cloudIdData['type']; + } + try { + list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId); + } catch (\InvalidArgumentException $e) { + continue; + } + + $localUser = $this->userManager->get($remoteUser); + /** + * Add local share if remote cloud id matches a local user ones + */ + if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId()) { + $result['wide'][] = [ + 'label' => $contact['FN'], + 'uuid' => $contact['UID'], + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $remoteUser + ] + ]; + } + + if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { + if (strtolower($cloudId) === $lowerSearch) { + $searchResult->markExactIdMatch($resultType); + } + $result['exact'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } else { + $result['wide'] = array_slice($result['wide'], $offset, $limit); + } + + /** + * Add generic share with remote item for valid cloud ids that are not users of the local instance + */ + if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { + try { + list($remoteUser, $serverUrl) = $this->splitUserRemote($search); + $localUser = $this->userManager->get($remoteUser); + if ($localUser === null || $search !== $localUser->getCloudId()) { + $result['exact'][] = [ + 'label' => $remoteUser . " ($serverUrl)", + 'uuid' => $remoteUser, + 'name' => $remoteUser, + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $search, + 'server' => $serverUrl, + ], + ]; + } + } catch (\InvalidArgumentException $e) { + } + } + + $searchResult->addResultSet($resultType, $result['wide'], $result['exact']); + + return true; + } + + /** + * split user and remote from federated cloud id + * + * @param string $address federated share address + * @return array [user, remoteURL] + * @throws \InvalidArgumentException + */ + public function splitUserRemote($address) { + try { + $cloudId = $this->cloudIdManager->resolveCloudId($address); + return [$cloudId->getUser(), $cloudId->getRemote()]; + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/Search.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/Search.php new file mode 100644 index 0000000..e777385 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/Search.php @@ -0,0 +1,112 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author onehappycat + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearch; +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IContainer; +use OCP\Share; + +class Search implements ISearch { + /** @var IContainer */ + private $c; + + protected $pluginList = []; + + public function __construct(IContainer $c) { + $this->c = $c; + } + + /** + * @param string $search + * @param array $shareTypes + * @param bool $lookup + * @param int|null $limit + * @param int|null $offset + * @return array + * @throws \OCP\AppFramework\QueryException + */ + public function search($search, array $shareTypes, $lookup, $limit, $offset) { + $hasMoreResults = false; + + // Trim leading and trailing whitespace characters, e.g. when query is copy-pasted + $search = trim($search); + + /** @var ISearchResult $searchResult */ + $searchResult = $this->c->resolve(SearchResult::class); + + foreach ($shareTypes as $type) { + if (!isset($this->pluginList[$type])) { + continue; + } + foreach ($this->pluginList[$type] as $plugin) { + /** @var ISearchPlugin $searchPlugin */ + $searchPlugin = $this->c->resolve($plugin); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + } + + // Get from lookup server, not a separate share type + if ($lookup) { + $searchPlugin = $this->c->resolve(LookupPlugin::class); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + + // sanitizing, could go into the plugins as well + + // if we have a exact match, either for the federated cloud id or for the + // email address we only return the exact match. It is highly unlikely + // that the exact same email address and federated cloud id exists + $emailType = new SearchResultType('emails'); + $remoteType = new SearchResultType('remotes'); + if ($searchResult->hasExactIdMatch($emailType) && !$searchResult->hasExactIdMatch($remoteType)) { + $searchResult->unsetResult($remoteType); + } elseif (!$searchResult->hasExactIdMatch($emailType) && $searchResult->hasExactIdMatch($remoteType)) { + $searchResult->unsetResult($emailType); + } + + // if we have an exact local user match with an email-a-like query, + // there is no need to show the remote and email matches. + $userType = new SearchResultType('users'); + if (strpos($search, '@') !== false && $searchResult->hasExactIdMatch($userType)) { + $searchResult->unsetResult($remoteType); + $searchResult->unsetResult($emailType); + } + + return [$searchResult->asArray(), (bool)$hasMoreResults]; + } + + public function registerPlugin(array $pluginInfo) { + $shareType = constant(Share::class . '::' . $pluginInfo['shareType']); + if ($shareType === null) { + throw new \InvalidArgumentException('Provided ShareType is invalid'); + } + $this->pluginList[$shareType][] = $pluginInfo['class']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/SearchResult.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/SearchResult.php new file mode 100644 index 0000000..8e2c5a1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/SearchResult.php @@ -0,0 +1,88 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; + +class SearchResult implements ISearchResult { + protected $result = [ + 'exact' => [], + ]; + + protected $exactIdMatches = []; + + public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null) { + $type = $type->getLabel(); + if (!isset($this->result[$type])) { + $this->result[$type] = []; + $this->result['exact'][$type] = []; + } + + $this->result[$type] = array_merge($this->result[$type], $matches); + if (is_array($exactMatches)) { + $this->result['exact'][$type] = array_merge($this->result['exact'][$type], $exactMatches); + } + } + + public function markExactIdMatch(SearchResultType $type) { + $this->exactIdMatches[$type->getLabel()] = 1; + } + + public function hasExactIdMatch(SearchResultType $type) { + return isset($this->exactIdMatches[$type->getLabel()]); + } + + public function hasResult(SearchResultType $type, $collaboratorId) { + $type = $type->getLabel(); + if (!isset($this->result[$type])) { + return false; + } + + $resultArrays = [$this->result['exact'][$type], $this->result[$type]]; + foreach ($resultArrays as $resultArray) { + foreach ($resultArray as $result) { + if ($result['value']['shareWith'] === $collaboratorId) { + return true; + } + } + } + + return false; + } + + public function asArray() { + return $this->result; + } + + public function unsetResult(SearchResultType $type) { + $type = $type->getLabel(); + $this->result[$type] = []; + if (isset($this->result['exact'][$type])) { + $this->result['exact'][$type] = []; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/UserPlugin.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/UserPlugin.php new file mode 100644 index 0000000..2d21c6a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -0,0 +1,249 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\IShare; +use OCP\UserStatus\IManager as IUserStatusManager; + +class UserPlugin implements ISearchPlugin { + /* @var bool */ + protected $shareWithGroupOnly; + protected $shareeEnumeration; + protected $shareeEnumerationInGroupOnly; + + /** @var IConfig */ + private $config; + /** @var IGroupManager */ + private $groupManager; + /** @var IUserSession */ + private $userSession; + /** @var IUserManager */ + private $userManager; + /** @var IUserStatusManager */ + private $userStatusManager; + + /** + * UserPlugin constructor. + * + * @param IConfig $config + * @param IUserManager $userManager + * @param IGroupManager $groupManager + * @param IUserSession $userSession + * @param IUserStatusManager $userStatusManager + */ + public function __construct(IConfig $config, + IUserManager $userManager, + IGroupManager $groupManager, + IUserSession $userSession, + IUserStatusManager $userStatusManager) { + $this->config = $config; + + $this->groupManager = $groupManager; + $this->userSession = $userSession; + $this->userManager = $userManager; + $this->userStatusManager = $userStatusManager; + + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $users = []; + $hasMoreResults = false; + + $currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + if ($this->shareWithGroupOnly) { + // Search in all the groups this user is part of + foreach ($currentUserGroups as $userGroupId) { + $usersInGroup = $this->groupManager->displayNamesInGroup($userGroupId, $search, $limit, $offset); + foreach ($usersInGroup as $userId => $displayName) { + $userId = (string) $userId; + $user = $this->userManager->get($userId); + if (!$user->isEnabled()) { + // Ignore disabled users + continue; + } + $users[$userId] = $user; + } + if (count($usersInGroup) >= $limit) { + $hasMoreResults = true; + } + } + } else { + // Search in all users + $usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset); + foreach ($usersTmp as $user) { + if ($user->isEnabled()) { // Don't keep deactivated users + $users[$user->getUID()] = $user; + } + } + } + + $this->takeOutCurrentUser($users); + + if (!$this->shareeEnumeration || count($users) < $limit) { + $hasMoreResults = true; + } + + $foundUserById = false; + $lowerSearch = strtolower($search); + $userStatuses = $this->userStatusManager->getUserStatuses(array_keys($users)); + foreach ($users as $uid => $user) { + $userDisplayName = $user->getDisplayName(); + $userEmail = $user->getEMailAddress(); + $uid = (string) $uid; + + $status = []; + if (array_key_exists($uid, $userStatuses)) { + $userStatus = $userStatuses[$uid]; + $status = [ + 'status' => $userStatus->getStatus(), + 'message' => $userStatus->getMessage(), + 'icon' => $userStatus->getIcon(), + 'clearAt' => $userStatus->getClearAt() + ? (int)$userStatus->getClearAt()->format('U') + : null, + ]; + } + + + if ( + $lowerSearch !== '' && (strtolower($uid) === $lowerSearch || + strtolower($userDisplayName) === $lowerSearch || + strtolower($userEmail) === $lowerSearch) + ) { + if (strtolower($uid) === $lowerSearch) { + $foundUserById = true; + } + $result['exact'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $uid, + ], + 'status' => $status, + ]; + } else { + $addToWideResults = false; + if ($this->shareeEnumeration && !$this->shareeEnumerationInGroupOnly) { + $addToWideResults = true; + } + + if ($this->shareeEnumerationInGroupOnly) { + $commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user)); + if (!empty($commonGroups)) { + $addToWideResults = true; + } + } + + if ($addToWideResults) { + $result['wide'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $uid, + ], + 'status' => $status, + ]; + } + } + } + + if ($offset === 0 && !$foundUserById) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $user = $this->userManager->get($search); + if ($user instanceof IUser) { + $addUser = true; + + if ($this->shareWithGroupOnly) { + // Only add, if we have a common group + $commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user)); + $addUser = !empty($commonGroups); + } + + if ($addUser) { + $status = []; + if (array_key_exists($user->getUID(), $userStatuses)) { + $userStatus = $userStatuses[$user->getUID()]; + $status = [ + 'status' => $userStatus->getStatus(), + 'message' => $userStatus->getMessage(), + 'icon' => $userStatus->getIcon(), + 'clearAt' => $userStatus->getClearAt() + ? (int)$userStatus->getClearAt()->format('U') + : null, + ]; + } + + $result['exact'][] = [ + 'label' => $user->getDisplayName(), + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $user->getUID(), + ], + 'status' => $status, + ]; + } + } + } + + + + $type = new SearchResultType('users'); + $searchResult->addResultSet($type, $result['wide'], $result['exact']); + if (count($result['exact'])) { + $searchResult->markExactIdMatch($type); + } + + return $hasMoreResults; + } + + public function takeOutCurrentUser(array &$users) { + $currentUser = $this->userSession->getUser(); + if (!is_null($currentUser)) { + if (isset($users[$currentUser->getUID()])) { + unset($users[$currentUser->getUID()]); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Collection.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Collection.php new file mode 100644 index 0000000..5bdc148 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Collection.php @@ -0,0 +1,232 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Resources; + +use Doctrine\DBAL\Exception\ConstraintViolationException; +use OCP\Collaboration\Resources\ICollection; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IResource; +use OCP\Collaboration\Resources\ResourceException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IUser; + +class Collection implements ICollection { + + /** @var IManager|Manager */ + protected $manager; + + /** @var IDBConnection */ + protected $connection; + + /** @var int */ + protected $id; + + /** @var string */ + protected $name; + + /** @var IUser|null */ + protected $userForAccess; + + /** @var bool|null */ + protected $access; + + /** @var IResource[] */ + protected $resources; + + public function __construct( + IManager $manager, + IDBConnection $connection, + int $id, + string $name, + ?IUser $userForAccess = null, + ?bool $access = null + ) { + $this->manager = $manager; + $this->connection = $connection; + $this->id = $id; + $this->name = $name; + $this->userForAccess = $userForAccess; + $this->access = $access; + $this->resources = []; + } + + /** + * @return int + * @since 16.0.0 + */ + public function getId(): int { + return $this->id; + } + + /** + * @return string + * @since 16.0.0 + */ + public function getName(): string { + return $this->name; + } + + /** + * @param string $name + * @since 16.0.0 + */ + public function setName(string $name): void { + $query = $this->connection->getQueryBuilder(); + $query->update(Manager::TABLE_COLLECTIONS) + ->set('name', $query->createNamedParameter($name)) + ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + + $this->name = $name; + } + + /** + * @return IResource[] + * @since 16.0.0 + */ + public function getResources(): array { + if (empty($this->resources)) { + $this->resources = $this->manager->getResourcesByCollectionForUser($this, $this->userForAccess); + } + + return $this->resources; + } + + /** + * Adds a resource to a collection + * + * @param IResource $resource + * @throws ResourceException when the resource is already part of the collection + * @since 16.0.0 + */ + public function addResource(IResource $resource): void { + array_map(function (IResource $r) use ($resource) { + if ($this->isSameResource($r, $resource)) { + throw new ResourceException('Already part of the collection'); + } + }, $this->getResources()); + + $this->resources[] = $resource; + + $query = $this->connection->getQueryBuilder(); + $query->insert(Manager::TABLE_RESOURCES) + ->values([ + 'collection_id' => $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT), + 'resource_type' => $query->createNamedParameter($resource->getType()), + 'resource_id' => $query->createNamedParameter($resource->getId()), + ]); + + try { + $query->execute(); + } catch (ConstraintViolationException $e) { + throw new ResourceException('Already part of the collection'); + } + + $this->manager->invalidateAccessCacheForCollection($this); + } + + /** + * Removes a resource from a collection + * + * @param IResource $resource + * @since 16.0.0 + */ + public function removeResource(IResource $resource): void { + $this->resources = array_filter($this->getResources(), function (IResource $r) use ($resource) { + return !$this->isSameResource($r, $resource); + }); + + $query = $this->connection->getQueryBuilder(); + $query->delete(Manager::TABLE_RESOURCES) + ->where($query->expr()->eq('collection_id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType()))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId()))); + $query->execute(); + + if (empty($this->resources)) { + $this->removeCollection(); + } else { + $this->manager->invalidateAccessCacheForCollection($this); + } + } + + /** + * Can a user/guest access the collection + * + * @param IUser|null $user + * @return bool + * @since 16.0.0 + */ + public function canAccess(?IUser $user): bool { + if ($user instanceof IUser) { + return $this->canUserAccess($user); + } + return $this->canGuestAccess(); + } + + protected function canUserAccess(IUser $user): bool { + if (\is_bool($this->access) && $this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) { + return $this->access; + } + + $access = $this->manager->canAccessCollection($this, $user); + if ($this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) { + $this->access = $access; + } + return $access; + } + + protected function canGuestAccess(): bool { + if (\is_bool($this->access) && !$this->userForAccess instanceof IUser) { + return $this->access; + } + + $access = $this->manager->canAccessCollection($this, null); + if (!$this->userForAccess instanceof IUser) { + $this->access = $access; + } + return $access; + } + + protected function isSameResource(IResource $resource1, IResource $resource2): bool { + return $resource1->getType() === $resource2->getType() && + $resource1->getId() === $resource2->getId(); + } + + protected function removeCollection(): void { + $query = $this->connection->getQueryBuilder(); + $query->delete(Manager::TABLE_COLLECTIONS) + ->where($query->expr()->eq('id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT))); + $query->execute(); + + $this->manager->invalidateAccessCacheForCollection($this); + $this->id = 0; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Listener.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Listener.php new file mode 100644 index 0000000..ffb5001 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Listener.php @@ -0,0 +1,69 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Resources; + +use OCP\Collaboration\Resources\IManager; +use OCP\IGroup; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Listener { + public static function register(EventDispatcherInterface $dispatcher): void { + $listener = function (GenericEvent $event) { + /** @var IUser $user */ + $user = $event->getArgument('user'); + /** @var IManager $resourceManager */ + $resourceManager = \OC::$server->query(IManager::class); + + $resourceManager->invalidateAccessCacheForUser($user); + }; + $dispatcher->addListener(IGroup::class . '::postAddUser', $listener); + $dispatcher->addListener(IGroup::class . '::postRemoveUser', $listener); + + $dispatcher->addListener(IUser::class . '::postDelete', function (GenericEvent $event) { + /** @var IUser $user */ + $user = $event->getSubject(); + /** @var IManager $resourceManager */ + $resourceManager = \OC::$server->query(IManager::class); + + $resourceManager->invalidateAccessCacheForUser($user); + }); + + $dispatcher->addListener(IGroup::class . '::preDelete', function (GenericEvent $event) { + /** @var IGroup $group */ + $group = $event->getSubject(); + /** @var IManager $resourceManager */ + $resourceManager = \OC::$server->query(IManager::class); + + foreach ($group->getUsers() as $user) { + $resourceManager->invalidateAccessCacheForUser($user); + } + }); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Manager.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Manager.php new file mode 100644 index 0000000..97213c4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Manager.php @@ -0,0 +1,526 @@ + + * + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Resources; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\Collaboration\Resources\CollectionException; +use OCP\Collaboration\Resources\ICollection; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IProvider; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\Collaboration\Resources\IResource; +use OCP\Collaboration\Resources\ResourceException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; + +class Manager implements IManager { + public const TABLE_COLLECTIONS = 'collres_collections'; + public const TABLE_RESOURCES = 'collres_resources'; + public const TABLE_ACCESS_CACHE = 'collres_accesscache'; + + /** @var IDBConnection */ + protected $connection; + /** @var IProviderManager */ + protected $providerManager; + /** @var ILogger */ + protected $logger; + + /** @var string[] */ + protected $providers = []; + + + public function __construct(IDBConnection $connection, IProviderManager $providerManager, ILogger $logger) { + $this->connection = $connection; + $this->providerManager = $providerManager; + $this->logger = $logger; + } + + /** + * @param int $id + * @return ICollection + * @throws CollectionException when the collection could not be found + * @since 16.0.0 + */ + public function getCollection(int $id): ICollection { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from(self::TABLE_COLLECTIONS) + ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if (!$row) { + throw new CollectionException('Collection not found'); + } + + return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name']); + } + + /** + * @param int $id + * @param IUser|null $user + * @return ICollection + * @throws CollectionException when the collection could not be found + * @since 16.0.0 + */ + public function getCollectionForUser(int $id, ?IUser $user): ICollection { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('*') + ->from(self::TABLE_COLLECTIONS, 'c') + ->leftJoin( + 'c', self::TABLE_ACCESS_CACHE, 'a', + $query->expr()->andX( + $query->expr()->eq('c.id', 'a.collection_id'), + $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ) + ) + ->where($query->expr()->eq('c.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if (!$row) { + throw new CollectionException('Collection not found'); + } + + $access = $row['access'] === null ? null : (bool) $row['access']; + if ($user instanceof IUser) { + return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access); + } + + return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access); + } + + /** + * @param IUser $user + * @param string $filter + * @param int $limit + * @param int $start + * @return ICollection[] + * @since 16.0.0 + */ + public function searchCollections(IUser $user, string $filter, int $limit = 50, int $start = 0): array { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('c.*', 'a.access') + ->from(self::TABLE_COLLECTIONS, 'c') + ->leftJoin( + 'c', self::TABLE_ACCESS_CACHE, 'a', + $query->expr()->andX( + $query->expr()->eq('c.id', 'a.collection_id'), + $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ) + ) + ->where($query->expr()->eq('a.access', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT))) + ->orderBy('c.id') + ->setMaxResults($limit) + ->setFirstResult($start); + + if ($filter !== '') { + $query->where($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%'))); + } + + $result = $query->execute(); + $collections = []; + + $foundResults = 0; + while ($row = $result->fetch()) { + $foundResults++; + $access = $row['access'] === null ? null : (bool) $row['access']; + $collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access); + if ($collection->canAccess($user)) { + $collections[] = $collection; + } + } + $result->closeCursor(); + + if (empty($collections) && $foundResults === $limit) { + return $this->searchCollections($user, $filter, $limit, $start + $limit); + } + + return $collections; + } + + /** + * @param string $name + * @return ICollection + * @since 16.0.0 + */ + public function newCollection(string $name): ICollection { + $query = $this->connection->getQueryBuilder(); + $query->insert(self::TABLE_COLLECTIONS) + ->values([ + 'name' => $query->createNamedParameter($name), + ]); + $query->execute(); + + return new Collection($this, $this->connection, $query->getLastInsertId(), $name); + } + + /** + * @param string $type + * @param string $id + * @return IResource + * @since 16.0.0 + */ + public function createResource(string $type, string $id): IResource { + return new Resource($this, $this->connection, $type, $id); + } + + /** + * @param string $type + * @param string $id + * @param IUser|null $user + * @return IResource + * @throws ResourceException + * @since 16.0.0 + */ + public function getResourceForUser(string $type, string $id, ?IUser $user): IResource { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('r.*', 'a.access') + ->from(self::TABLE_RESOURCES, 'r') + ->leftJoin( + 'r', self::TABLE_ACCESS_CACHE, 'a', + $query->expr()->andX( + $query->expr()->eq('r.resource_id', 'a.resource_id'), + $query->expr()->eq('r.resource_type', 'a.resource_type'), + $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ) + ) + ->where($query->expr()->eq('r.resource_type', $query->createNamedParameter($type, IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('r.resource_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if (!$row) { + throw new ResourceException('Resource not found'); + } + + $access = $row['access'] === null ? null : (bool) $row['access']; + if ($user instanceof IUser) { + return new Resource($this, $this->connection, $type, $id, $user, $access); + } + + return new Resource($this, $this->connection, $type, $id, null, $access); + } + + /** + * @param ICollection $collection + * @param IUser|null $user + * @return IResource[] + * @since 16.0.0 + */ + public function getResourcesByCollectionForUser(ICollection $collection, ?IUser $user): array { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('r.*', 'a.access') + ->from(self::TABLE_RESOURCES, 'r') + ->leftJoin( + 'r', self::TABLE_ACCESS_CACHE, 'a', + $query->expr()->andX( + $query->expr()->eq('r.resource_id', 'a.resource_id'), + $query->expr()->eq('r.resource_type', 'a.resource_type'), + $query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ) + ) + ->where($query->expr()->eq('r.collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT))); + + $resources = []; + $result = $query->execute(); + while ($row = $result->fetch()) { + $access = $row['access'] === null ? null : (bool) $row['access']; + $resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access); + } + $result->closeCursor(); + + return $resources; + } + + /** + * Get the rich object data of a resource + * + * @param IResource $resource + * @return array + * @since 16.0.0 + */ + public function getResourceRichObject(IResource $resource): array { + foreach ($this->providerManager->getResourceProviders() as $provider) { + if ($provider->getType() === $resource->getType()) { + try { + return $provider->getResourceRichObject($resource); + } catch (ResourceException $e) { + } + } + } + + return []; + } + + /** + * Can a user/guest access the collection + * + * @param IResource $resource + * @param IUser|null $user + * @return bool + * @since 16.0.0 + */ + public function canAccessResource(IResource $resource, ?IUser $user): bool { + $access = $this->checkAccessCacheForUserByResource($resource, $user); + if (\is_bool($access)) { + return $access; + } + + $access = false; + foreach ($this->providerManager->getResourceProviders() as $provider) { + if ($provider->getType() === $resource->getType()) { + try { + if ($provider->canAccessResource($resource, $user)) { + $access = true; + break; + } + } catch (ResourceException $e) { + } + } + } + + $this->cacheAccessForResource($resource, $user, $access); + return $access; + } + + /** + * Can a user/guest access the collection + * + * @param ICollection $collection + * @param IUser|null $user + * @return bool + * @since 16.0.0 + */ + public function canAccessCollection(ICollection $collection, ?IUser $user): bool { + $access = $this->checkAccessCacheForUserByCollection($collection, $user); + if (\is_bool($access)) { + return $access; + } + + $access = null; + // Access is granted when a user can access all resources + foreach ($collection->getResources() as $resource) { + if (!$resource->canAccess($user)) { + $access = false; + break; + } + + $access = true; + } + + $this->cacheAccessForCollection($collection, $user, $access); + return $access; + } + + protected function checkAccessCacheForUserByResource(IResource $resource, ?IUser $user): ?bool { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('access') + ->from(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId(), IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))) + ->setMaxResults(1); + + $hasAccess = null; + $result = $query->execute(); + if ($row = $result->fetch()) { + $hasAccess = (bool) $row['access']; + } + $result->closeCursor(); + + return $hasAccess; + } + + protected function checkAccessCacheForUserByCollection(ICollection $collection, ?IUser $user): ?bool { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->select('access') + ->from(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))) + ->setMaxResults(1); + + $hasAccess = null; + $result = $query->execute(); + if ($row = $result->fetch()) { + $hasAccess = (bool) $row['access']; + } + $result->closeCursor(); + + return $hasAccess; + } + + public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->insert(self::TABLE_ACCESS_CACHE) + ->values([ + 'user_id' => $query->createNamedParameter($userId), + 'resource_id' => $query->createNamedParameter($resource->getId()), + 'resource_type' => $query->createNamedParameter($resource->getType()), + 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL), + ]); + try { + $query->execute(); + } catch (UniqueConstraintViolationException $e) { + } + } + + public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->insert(self::TABLE_ACCESS_CACHE) + ->values([ + 'user_id' => $query->createNamedParameter($userId), + 'collection_id' => $query->createNamedParameter($collection->getId()), + 'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL), + ]); + try { + $query->execute(); + } catch (UniqueConstraintViolationException $e) { + } + } + + public function invalidateAccessCacheForUser(?IUser $user): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + $query->execute(); + } + + public function invalidateAccessCacheForResource(IResource $resource): void { + $query = $this->connection->getQueryBuilder(); + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId()))) + ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR))); + $query->execute(); + + foreach ($resource->getCollections() as $collection) { + $this->invalidateAccessCacheForCollection($collection); + } + } + + public function invalidateAccessCacheForAllCollections(): void { + $query = $this->connection->getQueryBuilder(); + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->neq('collection_id', $query->createNamedParameter(0))); + $query->execute(); + } + + public function invalidateAccessCacheForCollection(ICollection $collection): void { + $query = $this->connection->getQueryBuilder(); + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId()))); + $query->execute(); + } + + public function invalidateAccessCacheForProvider(IProvider $provider): void { + $query = $this->connection->getQueryBuilder(); + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR))); + $query->execute(); + } + + public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId()))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + $query->execute(); + + foreach ($resource->getCollections() as $collection) { + $this->invalidateAccessCacheForCollectionByUser($collection, $user); + } + } + + protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId()))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + $query->execute(); + } + + public function invalidateAccessCacheForProviderByUser(IProvider $provider, ?IUser $user): void { + $query = $this->connection->getQueryBuilder(); + $userId = $user instanceof IUser ? $user->getUID() : ''; + + $query->delete(self::TABLE_ACCESS_CACHE) + ->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + $query->execute(); + } + + /** + * @param string $provider + */ + public function registerResourceProvider(string $provider): void { + $this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]); + $this->providerManager->registerResourceProvider($provider); + } + + /** + * Get the resource type of the provider + * + * @return string + * @since 16.0.0 + */ + public function getType(): string { + return ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/ProviderManager.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/ProviderManager.php new file mode 100644 index 0000000..095ffdc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/ProviderManager.php @@ -0,0 +1,74 @@ + + * + * @author Daniel Kesselberg + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Resources; + +use OCP\AppFramework\QueryException; +use OCP\Collaboration\Resources\IProvider; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\ILogger; +use OCP\IServerContainer; + +class ProviderManager implements IProviderManager { + + /** @var string[] */ + protected $providers = []; + + /** @var IProvider[] */ + protected $providerInstances = []; + + /** @var IServerContainer */ + protected $serverContainer; + + /** @var ILogger */ + protected $logger; + + public function __construct(IServerContainer $serverContainer, ILogger $logger) { + $this->serverContainer = $serverContainer; + $this->logger = $logger; + } + + public function getResourceProviders(): array { + if ($this->providers !== []) { + foreach ($this->providers as $provider) { + try { + $this->providerInstances[] = $this->serverContainer->query($provider); + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'message' => "Could not query resource provider $provider: " . $e->getMessage() + ]); + } + } + $this->providers = []; + } + + return $this->providerInstances; + } + + public function registerResourceProvider(string $provider): void { + $this->providers[] = $provider; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Resource.php b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Resource.php new file mode 100644 index 0000000..702bc2f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Collaboration/Resources/Resource.php @@ -0,0 +1,163 @@ + + * + * @author Joas Schilling + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Resources; + +use OCP\Collaboration\Resources\ICollection; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IResource; +use OCP\IDBConnection; +use OCP\IUser; + +class Resource implements IResource { + + /** @var IManager */ + protected $manager; + + /** @var IDBConnection */ + protected $connection; + + /** @var string */ + protected $type; + + /** @var string */ + protected $id; + + /** @var IUser|null */ + protected $userForAccess; + + /** @var bool|null */ + protected $access; + + /** @var array|null */ + protected $data; + + public function __construct( + IManager $manager, + IDBConnection $connection, + string $type, + string $id, + ?IUser $userForAccess = null, + ?bool $access = null + ) { + $this->manager = $manager; + $this->connection = $connection; + $this->type = $type; + $this->id = $id; + $this->userForAccess = $userForAccess; + $this->access = $access; + } + + /** + * @return string + * @since 16.0.0 + */ + public function getType(): string { + return $this->type; + } + + /** + * @return string + * @since 16.0.0 + */ + public function getId(): string { + return $this->id; + } + + /** + * @return array + * @since 16.0.0 + */ + public function getRichObject(): array { + if ($this->data === null) { + $this->data = $this->manager->getResourceRichObject($this); + } + + return $this->data; + } + + /** + * Can a user/guest access the resource + * + * @param IUser|null $user + * @return bool + * @since 16.0.0 + */ + public function canAccess(?IUser $user): bool { + if ($user instanceof IUser) { + return $this->canUserAccess($user); + } + return $this->canGuestAccess(); + } + + protected function canUserAccess(IUser $user): bool { + if (\is_bool($this->access) && $this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) { + return $this->access; + } + + $access = $this->manager->canAccessResource($this, $user); + if ($this->userForAccess instanceof IUser && $user->getUID() === $this->userForAccess->getUID()) { + $this->access = $access; + } + return $access; + } + + protected function canGuestAccess(): bool { + if (\is_bool($this->access) && !$this->userForAccess instanceof IUser) { + return $this->access; + } + + $access = $this->manager->canAccessResource($this, null); + if (!$this->userForAccess instanceof IUser) { + $this->access = $access; + } + return $access; + } + + /** + * @return ICollection[] + * @since 16.0.0 + */ + public function getCollections(): array { + $collections = []; + + $query = $this->connection->getQueryBuilder(); + + $query->select('collection_id') + ->from('collres_resources') + ->where($query->expr()->eq('resource_type', $query->createNamedParameter($this->getType()))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($this->getId()))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $collections[] = $this->manager->getCollection((int) $row['collection_id']); + } + $result->closeCursor(); + + return $collections; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Color.php b/docker/overlays/nextcloud/html/lib/private/Color.php new file mode 100644 index 0000000..34ae487 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Color.php @@ -0,0 +1,36 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC; + +class Color { + public $r; + public $g; + public $b; + public function __construct($r, $g, $b) { + $this->r = $r; + $this->g = $g; + $this->b = $b; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/AsyncBus.php b/docker/overlays/nextcloud/html/lib/private/Command/AsyncBus.php new file mode 100644 index 0000000..6577d75 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/AsyncBus.php @@ -0,0 +1,104 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OCP\Command\IBus; +use OCP\Command\ICommand; + +/** + * Asynchronous command bus that uses the background job system as backend + */ +abstract class AsyncBus implements IBus { + /** + * List of traits for command which require sync execution + * + * @var string[] + */ + private $syncTraits = []; + + /** + * Schedule a command to be fired + * + * @param \OCP\Command\ICommand | callable $command + */ + public function push($command) { + if ($this->canRunAsync($command)) { + $this->queueCommand($command); + } else { + $this->runCommand($command); + } + } + + /** + * Queue a command in the bus + * + * @param \OCP\Command\ICommand | callable $command + */ + abstract protected function queueCommand($command); + + /** + * Require all commands using a trait to be run synchronous + * + * @param string $trait + */ + public function requireSync($trait) { + $this->syncTraits[] = trim($trait, '\\'); + } + + /** + * @param \OCP\Command\ICommand | callable $command + */ + private function runCommand($command) { + if ($command instanceof ICommand) { + $command->handle(); + } else { + $command(); + } + } + + /** + * @param \OCP\Command\ICommand | callable $command + * @return bool + */ + private function canRunAsync($command) { + $traits = $this->getTraits($command); + foreach ($traits as $trait) { + if (array_search($trait, $this->syncTraits) !== false) { + return false; + } + } + return true; + } + + /** + * @param \OCP\Command\ICommand | callable $command + * @return string[] + */ + private function getTraits($command) { + if ($command instanceof ICommand) { + return class_uses($command); + } else { + return []; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/CallableJob.php b/docker/overlays/nextcloud/html/lib/private/Command/CallableJob.php new file mode 100644 index 0000000..4445310 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/CallableJob.php @@ -0,0 +1,36 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OC\BackgroundJob\QueuedJob; + +class CallableJob extends QueuedJob { + protected function run($serializedCallable) { + $callable = unserialize($serializedCallable); + if (is_callable($callable)) { + $callable(); + } else { + throw new \InvalidArgumentException('Invalid serialized callable'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/ClosureJob.php b/docker/overlays/nextcloud/html/lib/private/Command/ClosureJob.php new file mode 100644 index 0000000..d67dad3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/ClosureJob.php @@ -0,0 +1,38 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OC\BackgroundJob\QueuedJob; +use SuperClosure\Serializer; + +class ClosureJob extends QueuedJob { + protected function run($serializedCallable) { + $serializer = new Serializer(); + $callable = $serializer->unserialize($serializedCallable); + if (is_callable($callable)) { + $callable(); + } else { + throw new \InvalidArgumentException('Invalid serialized callable'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/CommandJob.php b/docker/overlays/nextcloud/html/lib/private/Command/CommandJob.php new file mode 100644 index 0000000..7b2ae60 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/CommandJob.php @@ -0,0 +1,40 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OC\BackgroundJob\QueuedJob; +use OCP\Command\ICommand; + +/** + * Wrap a command in the background job interface + */ +class CommandJob extends QueuedJob { + protected function run($serializedCommand) { + $command = unserialize($serializedCommand); + if ($command instanceof ICommand) { + $command->handle(); + } else { + throw new \InvalidArgumentException('Invalid serialized command'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/CronBus.php b/docker/overlays/nextcloud/html/lib/private/Command/CronBus.php new file mode 100644 index 0000000..5bb8e4b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/CronBus.php @@ -0,0 +1,79 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Command; + +use OCP\Command\ICommand; +use SuperClosure\Serializer; + +class CronBus extends AsyncBus { + /** + * @var \OCP\BackgroundJob\IJobList + */ + private $jobList; + + + /** + * @param \OCP\BackgroundJob\IJobList $jobList + */ + public function __construct($jobList) { + $this->jobList = $jobList; + } + + protected function queueCommand($command) { + $this->jobList->add($this->getJobClass($command), $this->serializeCommand($command)); + } + + /** + * @param \OCP\Command\ICommand | callable $command + * @return string + */ + private function getJobClass($command) { + if ($command instanceof \Closure) { + return ClosureJob::class; + } elseif (is_callable($command)) { + return CallableJob::class; + } elseif ($command instanceof ICommand) { + return CommandJob::class; + } else { + throw new \InvalidArgumentException('Invalid command'); + } + } + + /** + * @param \OCP\Command\ICommand | callable $command + * @return string + */ + private function serializeCommand($command) { + if ($command instanceof \Closure) { + $serializer = new Serializer(); + return $serializer->serialize($command); + } elseif (is_callable($command) or $command instanceof ICommand) { + return serialize($command); + } else { + throw new \InvalidArgumentException('Invalid command'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/FileAccess.php b/docker/overlays/nextcloud/html/lib/private/Command/FileAccess.php new file mode 100644 index 0000000..b6bfa47 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/FileAccess.php @@ -0,0 +1,37 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OCP\IUser; + +trait FileAccess { + protected function setupFS(IUser $user) { + \OC_Util::setupFS($user->getUID()); + } + + protected function getUserFolder(IUser $user) { + $this->setupFS($user); + return \OC::$server->getUserFolder($user->getUID()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Command/QueueBus.php b/docker/overlays/nextcloud/html/lib/private/Command/QueueBus.php new file mode 100644 index 0000000..1955da7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Command/QueueBus.php @@ -0,0 +1,75 @@ + + * @author Julius Härtl + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Command; + +use OCP\Command\IBus; +use OCP\Command\ICommand; + +class QueueBus implements IBus { + /** + * @var ICommand[]|callable[] + */ + private $queue = []; + + /** + * Schedule a command to be fired + * + * @param \OCP\Command\ICommand | callable $command + */ + public function push($command) { + $this->queue[] = $command; + } + + /** + * Require all commands using a trait to be run synchronous + * + * @param string $trait + */ + public function requireSync($trait) { + } + + /** + * @param \OCP\Command\ICommand | callable $command + */ + private function runCommand($command) { + if ($command instanceof ICommand) { + // ensure the command can be serialized + $serialized = serialize($command); + if (strlen($serialized) > 4000) { + throw new \InvalidArgumentException('Trying to push a command which serialized form can not be stored in the database (>4000 character)'); + } + $unserialized = unserialize($serialized); + $unserialized->handle(); + } else { + $command(); + } + } + + public function run() { + while ($command = array_shift($this->queue)) { + $this->runCommand($command); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Comments/Comment.php b/docker/overlays/nextcloud/html/lib/private/Comments/Comment.php new file mode 100644 index 0000000..7c553c2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Comments/Comment.php @@ -0,0 +1,456 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Comments; + +use OCP\Comments\IComment; +use OCP\Comments\IllegalIDChangeException; +use OCP\Comments\MessageTooLongException; + +class Comment implements IComment { + protected $data = [ + 'id' => '', + 'parentId' => '0', + 'topmostParentId' => '0', + 'childrenCount' => '0', + 'message' => '', + 'verb' => '', + 'actorType' => '', + 'actorId' => '', + 'objectType' => '', + 'objectId' => '', + 'referenceId' => null, + 'creationDT' => null, + 'latestChildDT' => null, + ]; + + /** + * Comment constructor. + * + * @param array $data optional, array with keys according to column names from + * the comments database scheme + */ + public function __construct(array $data = null) { + if (is_array($data)) { + $this->fromArray($data); + } + } + + /** + * returns the ID of the comment + * + * It may return an empty string, if the comment was not stored. + * It is expected that the concrete Comment implementation gives an ID + * by itself (e.g. after saving). + * + * @return string + * @since 9.0.0 + */ + public function getId() { + return $this->data['id']; + } + + /** + * sets the ID of the comment and returns itself + * + * It is only allowed to set the ID only, if the current id is an empty + * string (which means it is not stored in a database, storage or whatever + * the concrete implementation does), or vice versa. Changing a given ID is + * not permitted and must result in an IllegalIDChangeException. + * + * @param string $id + * @return IComment + * @throws IllegalIDChangeException + * @since 9.0.0 + */ + public function setId($id) { + if (!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + + $id = trim($id); + if ($this->data['id'] === '' || ($this->data['id'] !== '' && $id === '')) { + $this->data['id'] = $id; + return $this; + } + + throw new IllegalIDChangeException('Not allowed to assign a new ID to an already saved comment.'); + } + + /** + * returns the parent ID of the comment + * + * @return string + * @since 9.0.0 + */ + public function getParentId() { + return $this->data['parentId']; + } + + /** + * sets the parent ID and returns itself + * + * @param string $parentId + * @return IComment + * @since 9.0.0 + */ + public function setParentId($parentId) { + if (!is_string($parentId)) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['parentId'] = trim($parentId); + return $this; + } + + /** + * returns the topmost parent ID of the comment + * + * @return string + * @since 9.0.0 + */ + public function getTopmostParentId() { + return $this->data['topmostParentId']; + } + + + /** + * sets the topmost parent ID and returns itself + * + * @param string $id + * @return IComment + * @since 9.0.0 + */ + public function setTopmostParentId($id) { + if (!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['topmostParentId'] = trim($id); + return $this; + } + + /** + * returns the number of children + * + * @return int + * @since 9.0.0 + */ + public function getChildrenCount() { + return $this->data['childrenCount']; + } + + /** + * sets the number of children + * + * @param int $count + * @return IComment + * @since 9.0.0 + */ + public function setChildrenCount($count) { + if (!is_int($count)) { + throw new \InvalidArgumentException('Integer expected.'); + } + $this->data['childrenCount'] = $count; + return $this; + } + + /** + * returns the message of the comment + * + * @return string + * @since 9.0.0 + */ + public function getMessage() { + return $this->data['message']; + } + + /** + * sets the message of the comment and returns itself + * + * @param string $message + * @param int $maxLength + * @return IComment + * @throws MessageTooLongException + * @since 9.0.0 + */ + public function setMessage($message, $maxLength = self::MAX_MESSAGE_LENGTH) { + if (!is_string($message)) { + throw new \InvalidArgumentException('String expected.'); + } + $message = trim($message); + if ($maxLength && mb_strlen($message, 'UTF-8') > $maxLength) { + throw new MessageTooLongException('Comment message must not exceed ' . $maxLength. ' characters'); + } + $this->data['message'] = $message; + return $this; + } + + /** + * returns an array containing mentions that are included in the comment + * + * @return array each mention provides a 'type' and an 'id', see example below + * @since 11.0.0 + * + * The return array looks like: + * [ + * [ + * 'type' => 'user', + * 'id' => 'citizen4' + * ], + * [ + * 'type' => 'group', + * 'id' => 'media' + * ], + * … + * ] + * + */ + public function getMentions() { + $ok = preg_match_all("/\B(?getMessage(), $mentions); + if (!$ok || !isset($mentions[0]) || !is_array($mentions[0])) { + return []; + } + $uids = array_unique($mentions[0]); + $result = []; + foreach ($uids as $uid) { + $cleanUid = trim(substr($uid, 1), '"'); + if (strpos($cleanUid, 'guest/') === 0) { + $result[] = ['type' => 'guest', 'id' => $cleanUid]; + } else { + $result[] = ['type' => 'user', 'id' => $cleanUid]; + } + } + return $result; + } + + /** + * returns the verb of the comment + * + * @return string + * @since 9.0.0 + */ + public function getVerb() { + return $this->data['verb']; + } + + /** + * sets the verb of the comment, e.g. 'comment' or 'like' + * + * @param string $verb + * @return IComment + * @since 9.0.0 + */ + public function setVerb($verb) { + if (!is_string($verb) || !trim($verb)) { + throw new \InvalidArgumentException('Non-empty String expected.'); + } + $this->data['verb'] = trim($verb); + return $this; + } + + /** + * returns the actor type + * + * @return string + * @since 9.0.0 + */ + public function getActorType() { + return $this->data['actorType']; + } + + /** + * returns the actor ID + * + * @return string + * @since 9.0.0 + */ + public function getActorId() { + return $this->data['actorId']; + } + + /** + * sets (overwrites) the actor type and id + * + * @param string $actorType e.g. 'users' + * @param string $actorId e.g. 'zombie234' + * @return IComment + * @since 9.0.0 + */ + public function setActor($actorType, $actorId) { + if ( + !is_string($actorType) || !trim($actorType) + || !is_string($actorId) || $actorId === '' + ) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['actorType'] = trim($actorType); + $this->data['actorId'] = $actorId; + return $this; + } + + /** + * returns the creation date of the comment. + * + * If not explicitly set, it shall default to the time of initialization. + * + * @return \DateTime + * @since 9.0.0 + */ + public function getCreationDateTime() { + return $this->data['creationDT']; + } + + /** + * sets the creation date of the comment and returns itself + * + * @param \DateTime $timestamp + * @return IComment + * @since 9.0.0 + */ + public function setCreationDateTime(\DateTime $timestamp) { + $this->data['creationDT'] = $timestamp; + return $this; + } + + /** + * returns the DateTime of the most recent child, if set, otherwise null + * + * @return \DateTime|null + * @since 9.0.0 + */ + public function getLatestChildDateTime() { + return $this->data['latestChildDT']; + } + + /** + * sets the date of the most recent child + * + * @param \DateTime $dateTime + * @return IComment + * @since 9.0.0 + */ + public function setLatestChildDateTime(\DateTime $dateTime = null) { + $this->data['latestChildDT'] = $dateTime; + return $this; + } + + /** + * returns the object type the comment is attached to + * + * @return string + * @since 9.0.0 + */ + public function getObjectType() { + return $this->data['objectType']; + } + + /** + * returns the object id the comment is attached to + * + * @return string + * @since 9.0.0 + */ + public function getObjectId() { + return $this->data['objectId']; + } + + /** + * sets (overwrites) the object of the comment + * + * @param string $objectType e.g. 'files' + * @param string $objectId e.g. '16435' + * @return IComment + * @since 9.0.0 + */ + public function setObject($objectType, $objectId) { + if ( + !is_string($objectType) || !trim($objectType) + || !is_string($objectId) || trim($objectId) === '' + ) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['objectType'] = trim($objectType); + $this->data['objectId'] = trim($objectId); + return $this; + } + + /** + * returns the reference id of the comment + * + * @return string|null + * @since 19.0.0 + */ + public function getReferenceId(): ?string { + return $this->data['referenceId']; + } + + /** + * sets (overwrites) the reference id of the comment + * + * @param string $referenceId e.g. sha256 hash sum + * @return IComment + * @since 19.0.0 + */ + public function setReferenceId(?string $referenceId): IComment { + if ($referenceId === null) { + $this->data['referenceId'] = $referenceId; + } else { + $referenceId = trim($referenceId); + if ($referenceId === '') { + throw new \InvalidArgumentException('Non empty string expected.'); + } + $this->data['referenceId'] = $referenceId; + } + return $this; + } + + /** + * sets the comment data based on an array with keys as taken from the + * database. + * + * @param array $data + * @return IComment + */ + protected function fromArray($data) { + foreach (array_keys($data) as $key) { + // translate DB keys to internal setter names + $setter = 'set' . implode('', array_map('ucfirst', explode('_', $key))); + $setter = str_replace('Timestamp', 'DateTime', $setter); + + if (method_exists($this, $setter)) { + $this->$setter($data[$key]); + } + } + + foreach (['actor', 'object'] as $role) { + if (isset($data[$role . '_type']) && isset($data[$role . '_id'])) { + $setter = 'set' . ucfirst($role); + $this->$setter($data[$role . '_type'], $data[$role . '_id']); + } + } + + return $this; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Comments/Manager.php b/docker/overlays/nextcloud/html/lib/private/Comments/Manager.php new file mode 100644 index 0000000..1acfe79 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Comments/Manager.php @@ -0,0 +1,1121 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Comments; + +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use OCP\Comments\CommentsEvent; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsEventHandler; +use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; + +class Manager implements ICommentsManager { + + /** @var IDBConnection */ + protected $dbConn; + + /** @var ILogger */ + protected $logger; + + /** @var IConfig */ + protected $config; + + /** @var IComment[] */ + protected $commentsCache = []; + + /** @var \Closure[] */ + protected $eventHandlerClosures = []; + + /** @var ICommentsEventHandler[] */ + protected $eventHandlers = []; + + /** @var \Closure[] */ + protected $displayNameResolvers = []; + + /** + * Manager constructor. + * + * @param IDBConnection $dbConn + * @param ILogger $logger + * @param IConfig $config + */ + public function __construct( + IDBConnection $dbConn, + ILogger $logger, + IConfig $config + ) { + $this->dbConn = $dbConn; + $this->logger = $logger; + $this->config = $config; + } + + /** + * converts data base data into PHP native, proper types as defined by + * IComment interface. + * + * @param array $data + * @return array + */ + protected function normalizeDatabaseData(array $data) { + $data['id'] = (string)$data['id']; + $data['parent_id'] = (string)$data['parent_id']; + $data['topmost_parent_id'] = (string)$data['topmost_parent_id']; + $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']); + if (!is_null($data['latest_child_timestamp'])) { + $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); + } + $data['children_count'] = (int)$data['children_count']; + $data['reference_id'] = $data['reference_id'] ?? null; + return $data; + } + + + /** + * @param array $data + * @return IComment + */ + public function getCommentFromData(array $data): IComment { + return new Comment($this->normalizeDatabaseData($data)); + } + + /** + * prepares a comment for an insert or update operation after making sure + * all necessary fields have a value assigned. + * + * @param IComment $comment + * @return IComment returns the same updated IComment instance as provided + * by parameter for convenience + * @throws \UnexpectedValueException + */ + protected function prepareCommentForDatabaseWrite(IComment $comment) { + if (!$comment->getActorType() + || $comment->getActorId() === '' + || !$comment->getObjectType() + || $comment->getObjectId() === '' + || !$comment->getVerb() + ) { + throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); + } + + if ($comment->getId() === '') { + $comment->setChildrenCount(0); + $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC'))); + $comment->setLatestChildDateTime(null); + } + + if (is_null($comment->getCreationDateTime())) { + $comment->setCreationDateTime(new \DateTime()); + } + + if ($comment->getParentId() !== '0') { + $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId())); + } else { + $comment->setTopmostParentId('0'); + } + + $this->cache($comment); + + return $comment; + } + + /** + * returns the topmost parent id of a given comment identified by ID + * + * @param string $id + * @return string + * @throws NotFoundException + */ + protected function determineTopmostParentId($id) { + $comment = $this->get($id); + if ($comment->getParentId() === '0') { + return $comment->getId(); + } + + return $this->determineTopmostParentId($comment->getParentId()); + } + + /** + * updates child information of a comment + * + * @param string $id + * @param \DateTime $cDateTime the date time of the most recent child + * @throws NotFoundException + */ + protected function updateChildrenInformation($id, \DateTime $cDateTime) { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select($qb->func()->count('id')) + ->from('comments') + ->where($qb->expr()->eq('parent_id', $qb->createParameter('id'))) + ->setParameter('id', $id); + + $resultStatement = $query->execute(); + $data = $resultStatement->fetch(\PDO::FETCH_NUM); + $resultStatement->closeCursor(); + $children = (int)$data[0]; + + $comment = $this->get($id); + $comment->setChildrenCount($children); + $comment->setLatestChildDateTime($cDateTime); + $this->save($comment); + } + + /** + * Tests whether actor or object type and id parameters are acceptable. + * Throws exception if not. + * + * @param string $role + * @param string $type + * @param string $id + * @throws \InvalidArgumentException + */ + protected function checkRoleParameters($role, $type, $id) { + if ( + !is_string($type) || empty($type) + || !is_string($id) || empty($id) + ) { + throw new \InvalidArgumentException($role . ' parameters must be string and not empty'); + } + } + + /** + * run-time caches a comment + * + * @param IComment $comment + */ + protected function cache(IComment $comment) { + $id = $comment->getId(); + if (empty($id)) { + return; + } + $this->commentsCache[(string)$id] = $comment; + } + + /** + * removes an entry from the comments run time cache + * + * @param mixed $id the comment's id + */ + protected function uncache($id) { + $id = (string)$id; + if (isset($this->commentsCache[$id])) { + unset($this->commentsCache[$id]); + } + } + + /** + * returns a comment instance + * + * @param string $id the ID of the comment + * @return IComment + * @throws NotFoundException + * @throws \InvalidArgumentException + * @since 9.0.0 + */ + public function get($id) { + if ((int)$id === 0) { + throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.'); + } + + if (isset($this->commentsCache[$id])) { + return $this->commentsCache[$id]; + } + + $qb = $this->dbConn->getQueryBuilder(); + $resultStatement = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $id, IQueryBuilder::PARAM_INT) + ->execute(); + + $data = $resultStatement->fetch(); + $resultStatement->closeCursor(); + if (!$data) { + throw new NotFoundException(); + } + + + $comment = $this->getCommentFromData($data); + $this->cache($comment); + return $comment; + } + + /** + * returns the comment specified by the id and all it's child comments. + * At this point of time, we do only support one level depth. + * + * @param string $id + * @param int $limit max number of entries to return, 0 returns all + * @param int $offset the start entry + * @return array + * @since 9.0.0 + * + * The return array looks like this + * [ + * 'comment' => IComment, // root comment + * 'replies' => + * [ + * 0 => + * [ + * 'comment' => IComment, + * 'replies' => [] + * ] + * 1 => + * [ + * 'comment' => IComment, + * 'replies'=> [] + * ], + * … + * ] + * ] + */ + public function getTree($id, $limit = 0, $offset = 0) { + $tree = []; + $tree['comment'] = $this->get($id); + $tree['replies'] = []; + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id'))) + ->orderBy('creation_timestamp', 'DESC') + ->setParameter('id', $id); + + if ($limit > 0) { + $query->setMaxResults($limit); + } + if ($offset > 0) { + $query->setFirstResult($offset); + } + + $resultStatement = $query->execute(); + while ($data = $resultStatement->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $tree['replies'][] = [ + 'comment' => $comment, + 'replies' => [] + ]; + } + $resultStatement->closeCursor(); + + return $tree; + } + + /** + * returns comments for a specific object (e.g. a file). + * + * The sort order is always newest to oldest. + * + * @param string $objectType the object type, e.g. 'files' + * @param string $objectId the id of the object + * @param int $limit optional, number of maximum comments to be returned. if + * not specified, all comments are returned. + * @param int $offset optional, starting point + * @param \DateTime $notOlderThan optional, timestamp of the oldest comments + * that may be returned + * @return IComment[] + * @since 9.0.0 + */ + public function getForObject( + $objectType, + $objectId, + $limit = 0, + $offset = 0, + \DateTime $notOlderThan = null + ) { + $comments = []; + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->orderBy('creation_timestamp', 'DESC') + ->setParameter('type', $objectType) + ->setParameter('id', $objectId); + + if ($limit > 0) { + $query->setMaxResults($limit); + } + if ($offset > 0) { + $query->setFirstResult($offset); + } + if (!is_null($notOlderThan)) { + $query + ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) + ->setParameter('notOlderThan', $notOlderThan, 'datetime'); + } + + $resultStatement = $query->execute(); + while ($data = $resultStatement->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $comments[] = $comment; + } + $resultStatement->closeCursor(); + + return $comments; + } + + /** + * @param string $objectType the object type, e.g. 'files' + * @param string $objectId the id of the object + * @param int $lastKnownCommentId the last known comment (will be used as offset) + * @param string $sortDirection direction of the comments (`asc` or `desc`) + * @param int $limit optional, number of maximum comments to be returned. if + * set to 0, all comments are returned. + * @return IComment[] + * @return array + */ + public function getForObjectSince( + string $objectType, + string $objectId, + int $lastKnownCommentId, + string $sortDirection = 'asc', + int $limit = 30 + ): array { + $comments = []; + + $query = $this->dbConn->getQueryBuilder(); + $query->select('*') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->orderBy('creation_timestamp', $sortDirection === 'desc' ? 'DESC' : 'ASC') + ->addOrderBy('id', $sortDirection === 'desc' ? 'DESC' : 'ASC'); + + if ($limit > 0) { + $query->setMaxResults($limit); + } + + $lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment( + $objectType, + $objectId, + $lastKnownCommentId + ) : null; + if ($lastKnownComment instanceof IComment) { + $lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime(); + if ($sortDirection === 'desc') { + $query->andWhere( + $query->expr()->orX( + $query->expr()->lt( + 'creation_timestamp', + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), + IQueryBuilder::PARAM_DATE + ), + $query->expr()->andX( + $query->expr()->eq( + 'creation_timestamp', + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), + IQueryBuilder::PARAM_DATE + ), + $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId)) + ) + ) + ); + } else { + $query->andWhere( + $query->expr()->orX( + $query->expr()->gt( + 'creation_timestamp', + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), + IQueryBuilder::PARAM_DATE + ), + $query->expr()->andX( + $query->expr()->eq( + 'creation_timestamp', + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), + IQueryBuilder::PARAM_DATE + ), + $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId)) + ) + ) + ); + } + } + + $resultStatement = $query->execute(); + while ($data = $resultStatement->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $comments[] = $comment; + } + $resultStatement->closeCursor(); + + return $comments; + } + + /** + * @param string $objectType the object type, e.g. 'files' + * @param string $objectId the id of the object + * @param int $id the comment to look for + * @return Comment|null + */ + protected function getLastKnownComment(string $objectType, + string $objectId, + int $id) { + $query = $this->dbConn->getQueryBuilder(); + $query->select('*') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $comment = $this->getCommentFromData($row); + $this->cache($comment); + return $comment; + } + + return null; + } + + /** + * Search for comments with a given content + * + * @param string $search content to search for + * @param string $objectType Limit the search by object type + * @param string $objectId Limit the search by object id + * @param string $verb Limit the verb of the comment + * @param int $offset + * @param int $limit + * @return IComment[] + */ + public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array { + $query = $this->dbConn->getQueryBuilder(); + + $query->select('*') + ->from('comments') + ->where($query->expr()->iLike('message', $query->createNamedParameter( + '%' . $this->dbConn->escapeLikeParameter($search). '%' + ))) + ->orderBy('creation_timestamp', 'DESC') + ->addOrderBy('id', 'DESC') + ->setMaxResults($limit); + + if ($objectType !== '') { + $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType))); + } + if ($objectId !== '') { + $query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))); + } + if ($verb !== '') { + $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb))); + } + if ($offset !== 0) { + $query->setFirstResult($offset); + } + + $comments = []; + $result = $query->execute(); + while ($data = $result->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $comments[] = $comment; + } + $result->closeCursor(); + + return $comments; + } + + /** + * @param $objectType string the object type, e.g. 'files' + * @param $objectId string the id of the object + * @param \DateTime $notOlderThan optional, timestamp of the oldest comments + * that may be returned + * @param string $verb Limit the verb of the comment - Added in 14.0.0 + * @return Int + * @since 9.0.0 + */ + public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select($qb->func()->count('id')) + ->from('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->setParameter('type', $objectType) + ->setParameter('id', $objectId); + + if (!is_null($notOlderThan)) { + $query + ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) + ->setParameter('notOlderThan', $notOlderThan, 'datetime'); + } + + if ($verb !== '') { + $query->andWhere($qb->expr()->eq('verb', $qb->createNamedParameter($verb))); + } + + $resultStatement = $query->execute(); + $data = $resultStatement->fetch(\PDO::FETCH_NUM); + $resultStatement->closeCursor(); + return (int)$data[0]; + } + + /** + * Get the number of unread comments for all files in a folder + * + * @param int $folderId + * @param IUser $user + * @return array [$fileId => $unreadCount] + */ + public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + + $query = $qb->select('f.fileid') + ->addSelect($qb->func()->count('c.id', 'num_ids')) + ->from('filecache', 'f') + ->leftJoin('f', 'comments', 'c', $qb->expr()->andX( + $qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)), + $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')) + )) + ->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX( + $qb->expr()->eq('c.object_id', 'm.object_id'), + $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')) + )) + ->where( + $qb->expr()->andX( + $qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)), + $qb->expr()->orX( + $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')), + $qb->expr()->isNull('c.object_type') + ), + $qb->expr()->orX( + $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')), + $qb->expr()->isNull('m.object_type') + ), + $qb->expr()->orX( + $qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())), + $qb->expr()->isNull('m.user_id') + ), + $qb->expr()->orX( + $qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'), + $qb->expr()->isNull('m.marker_datetime') + ) + ) + )->groupBy('f.fileid'); + + $resultStatement = $query->execute(); + + $results = []; + while ($row = $resultStatement->fetch()) { + $results[$row['fileid']] = (int) $row['num_ids']; + } + $resultStatement->closeCursor(); + return $results; + } + + /** + * creates a new comment and returns it. At this point of time, it is not + * saved in the used data storage. Use save() after setting other fields + * of the comment (e.g. message or verb). + * + * @param string $actorType the actor type (e.g. 'users') + * @param string $actorId a user id + * @param string $objectType the object type the comment is attached to + * @param string $objectId the object id the comment is attached to + * @return IComment + * @since 9.0.0 + */ + public function create($actorType, $actorId, $objectType, $objectId) { + $comment = new Comment(); + $comment + ->setActor($actorType, $actorId) + ->setObject($objectType, $objectId); + return $comment; + } + + /** + * permanently deletes the comment specified by the ID + * + * When the comment has child comments, their parent ID will be changed to + * the parent ID of the item that is to be deleted. + * + * @param string $id + * @return bool + * @throws \InvalidArgumentException + * @since 9.0.0 + */ + public function delete($id) { + if (!is_string($id)) { + throw new \InvalidArgumentException('Parameter must be string'); + } + + try { + $comment = $this->get($id); + } catch (\Exception $e) { + // Ignore exceptions, we just don't fire a hook then + $comment = null; + } + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments') + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $id); + + try { + $affectedRows = $query->execute(); + $this->uncache($id); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + + if ($affectedRows > 0 && $comment instanceof IComment) { + $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment); + } + + return ($affectedRows > 0); + } + + /** + * saves the comment permanently + * + * if the supplied comment has an empty ID, a new entry comment will be + * saved and the instance updated with the new ID. + * + * Otherwise, an existing comment will be updated. + * + * Throws NotFoundException when a comment that is to be updated does not + * exist anymore at this point of time. + * + * @param IComment $comment + * @return bool + * @throws NotFoundException + * @since 9.0.0 + */ + public function save(IComment $comment) { + if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') { + $result = $this->insert($comment); + } else { + $result = $this->update($comment); + } + + if ($result && !!$comment->getParentId()) { + $this->updateChildrenInformation( + $comment->getParentId(), + $comment->getCreationDateTime() + ); + $this->cache($comment); + } + + return $result; + } + + /** + * inserts the provided comment in the database + * + * @param IComment $comment + * @return bool + */ + protected function insert(IComment $comment): bool { + try { + $result = $this->insertQuery($comment, true); + } catch (InvalidFieldNameException $e) { + // The reference id field was only added in Nextcloud 19. + // In order to not cause too long waiting times on the update, + // it was decided to only add it lazy, as it is also not a critical + // feature, but only helps to have a better experience while commenting. + // So in case the reference_id field is missing, + // we simply save the comment without that field. + $result = $this->insertQuery($comment, false); + } + + return $result; + } + + protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool { + $qb = $this->dbConn->getQueryBuilder(); + + $values = [ + 'parent_id' => $qb->createNamedParameter($comment->getParentId()), + 'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()), + 'children_count' => $qb->createNamedParameter($comment->getChildrenCount()), + 'actor_type' => $qb->createNamedParameter($comment->getActorType()), + 'actor_id' => $qb->createNamedParameter($comment->getActorId()), + 'message' => $qb->createNamedParameter($comment->getMessage()), + 'verb' => $qb->createNamedParameter($comment->getVerb()), + 'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'), + 'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'), + 'object_type' => $qb->createNamedParameter($comment->getObjectType()), + 'object_id' => $qb->createNamedParameter($comment->getObjectId()), + ]; + + if ($tryWritingReferenceId) { + $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId()); + } + + $affectedRows = $qb->insert('comments') + ->values($values) + ->execute(); + + if ($affectedRows > 0) { + $comment->setId((string)$qb->getLastInsertId()); + $this->sendEvent(CommentsEvent::EVENT_ADD, $comment); + } + + return $affectedRows > 0; + } + + /** + * updates a Comment data row + * + * @param IComment $comment + * @return bool + * @throws NotFoundException + */ + protected function update(IComment $comment) { + // for properly working preUpdate Events we need the old comments as is + // in the DB and overcome caching. Also avoid that outdated information stays. + $this->uncache($comment->getId()); + $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId())); + $this->uncache($comment->getId()); + + try { + $result = $this->updateQuery($comment, true); + } catch (InvalidFieldNameException $e) { + // See function insert() for explanation + $result = $this->updateQuery($comment, false); + } + + $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment); + + return $result; + } + + protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool { + $qb = $this->dbConn->getQueryBuilder(); + $qb + ->update('comments') + ->set('parent_id', $qb->createNamedParameter($comment->getParentId())) + ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId())) + ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount())) + ->set('actor_type', $qb->createNamedParameter($comment->getActorType())) + ->set('actor_id', $qb->createNamedParameter($comment->getActorId())) + ->set('message', $qb->createNamedParameter($comment->getMessage())) + ->set('verb', $qb->createNamedParameter($comment->getVerb())) + ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime')) + ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime')) + ->set('object_type', $qb->createNamedParameter($comment->getObjectType())) + ->set('object_id', $qb->createNamedParameter($comment->getObjectId())); + + if ($tryWritingReferenceId) { + $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId())); + } + + $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId()))) + ->execute(); + + if ($affectedRows === 0) { + throw new NotFoundException('Comment to update does ceased to exist'); + } + + return $affectedRows > 0; + } + + /** + * removes references to specific actor (e.g. on user delete) of a comment. + * The comment itself must not get lost/deleted. + * + * @param string $actorType the actor type (e.g. 'users') + * @param string $actorId a user id + * @return boolean + * @since 9.0.0 + */ + public function deleteReferencesOfActor($actorType, $actorId) { + $this->checkRoleParameters('Actor', $actorType, $actorId); + + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->update('comments') + ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) + ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) + ->where($qb->expr()->eq('actor_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id'))) + ->setParameter('type', $actorType) + ->setParameter('id', $actorId) + ->execute(); + + $this->commentsCache = []; + + return is_int($affectedRows); + } + + /** + * deletes all comments made of a specific object (e.g. on file delete) + * + * @param string $objectType the object type (e.g. 'files') + * @param string $objectId e.g. the file id + * @return boolean + * @since 9.0.0 + */ + public function deleteCommentsAtObject($objectType, $objectId) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->delete('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->setParameter('type', $objectType) + ->setParameter('id', $objectId) + ->execute(); + + $this->commentsCache = []; + + return is_int($affectedRows); + } + + /** + * deletes the read markers for the specified user + * + * @param \OCP\IUser $user + * @return bool + * @since 9.0.0 + */ + public function deleteReadMarksFromUser(IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments_read_markers') + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->setParameter('user_id', $user->getUID()); + + try { + $affectedRows = $query->execute(); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + return ($affectedRows > 0); + } + + /** + * sets the read marker for a given file to the specified date for the + * provided user + * + * @param string $objectType + * @param string $objectId + * @param \DateTime $dateTime + * @param IUser $user + * @since 9.0.0 + */ + public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $values = [ + 'user_id' => $qb->createNamedParameter($user->getUID()), + 'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'), + 'object_type' => $qb->createNamedParameter($objectType), + 'object_id' => $qb->createNamedParameter($objectId), + ]; + + // Strategy: try to update, if this does not return affected rows, do an insert. + $affectedRows = $qb + ->update('comments_read_markers') + ->set('user_id', $values['user_id']) + ->set('marker_datetime', $values['marker_datetime']) + ->set('object_type', $values['object_type']) + ->set('object_id', $values['object_id']) + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) + ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) + ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) + ->execute(); + + if ($affectedRows > 0) { + return; + } + + $qb->insert('comments_read_markers') + ->values($values) + ->execute(); + } + + /** + * returns the read marker for a given file to the specified date for the + * provided user. It returns null, when the marker is not present, i.e. + * no comments were marked as read. + * + * @param string $objectType + * @param string $objectId + * @param IUser $user + * @return \DateTime|null + * @since 9.0.0 + */ + public function getReadMark($objectType, $objectId, IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + $resultStatement = $qb->select('marker_datetime') + ->from('comments_read_markers') + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) + ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) + ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) + ->execute(); + + $data = $resultStatement->fetch(); + $resultStatement->closeCursor(); + if (!$data || is_null($data['marker_datetime'])) { + return null; + } + + return new \DateTime($data['marker_datetime']); + } + + /** + * deletes the read markers on the specified object + * + * @param string $objectType + * @param string $objectId + * @return bool + * @since 9.0.0 + */ + public function deleteReadMarksOnObject($objectType, $objectId) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments_read_markers') + ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('object_type', $objectType) + ->setParameter('object_id', $objectId); + + try { + $affectedRows = $query->execute(); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + return ($affectedRows > 0); + } + + /** + * registers an Entity to the manager, so event notifications can be send + * to consumers of the comments infrastructure + * + * @param \Closure $closure + */ + public function registerEventHandler(\Closure $closure) { + $this->eventHandlerClosures[] = $closure; + $this->eventHandlers = []; + } + + /** + * registers a method that resolves an ID to a display name for a given type + * + * @param string $type + * @param \Closure $closure + * @throws \OutOfBoundsException + * @since 11.0.0 + * + * Only one resolver shall be registered per type. Otherwise a + * \OutOfBoundsException has to thrown. + */ + public function registerDisplayNameResolver($type, \Closure $closure) { + if (!is_string($type)) { + throw new \InvalidArgumentException('String expected.'); + } + if (isset($this->displayNameResolvers[$type])) { + throw new \OutOfBoundsException('Displayname resolver for this type already registered'); + } + $this->displayNameResolvers[$type] = $closure; + } + + /** + * resolves a given ID of a given Type to a display name. + * + * @param string $type + * @param string $id + * @return string + * @throws \OutOfBoundsException + * @since 11.0.0 + * + * If a provided type was not registered, an \OutOfBoundsException shall + * be thrown. It is upon the resolver discretion what to return of the + * provided ID is unknown. It must be ensured that a string is returned. + */ + public function resolveDisplayName($type, $id) { + if (!is_string($type)) { + throw new \InvalidArgumentException('String expected.'); + } + if (!isset($this->displayNameResolvers[$type])) { + throw new \OutOfBoundsException('No Displayname resolver for this type registered'); + } + return (string)$this->displayNameResolvers[$type]($id); + } + + /** + * returns valid, registered entities + * + * @return \OCP\Comments\ICommentsEventHandler[] + */ + private function getEventHandlers() { + if (!empty($this->eventHandlers)) { + return $this->eventHandlers; + } + + $this->eventHandlers = []; + foreach ($this->eventHandlerClosures as $name => $closure) { + $entity = $closure(); + if (!($entity instanceof ICommentsEventHandler)) { + throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface'); + } + $this->eventHandlers[$name] = $entity; + } + + return $this->eventHandlers; + } + + /** + * sends notifications to the registered entities + * + * @param $eventType + * @param IComment $comment + */ + private function sendEvent($eventType, IComment $comment) { + $entities = $this->getEventHandlers(); + $event = new CommentsEvent($eventType, $comment); + foreach ($entities as $entity) { + $entity->handle($event); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Comments/ManagerFactory.php b/docker/overlays/nextcloud/html/lib/private/Comments/ManagerFactory.php new file mode 100644 index 0000000..dd69a4f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Comments/ManagerFactory.php @@ -0,0 +1,63 @@ + + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Comments; + +use OCP\Comments\ICommentsManager; +use OCP\Comments\ICommentsManagerFactory; +use OCP\IServerContainer; + +class ManagerFactory implements ICommentsManagerFactory { + + /** + * Server container + * + * @var IServerContainer + */ + private $serverContainer; + + /** + * Constructor for the comments manager factory + * + * @param IServerContainer $serverContainer server container + */ + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + /** + * creates and returns an instance of the ICommentsManager + * + * @return ICommentsManager + * @since 9.0.0 + */ + public function getManager() { + return new Manager( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getLogger(), + $this->serverContainer->getConfig() + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Config.php b/docker/overlays/nextcloud/html/lib/private/Config.php new file mode 100644 index 0000000..cbdbc5b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Config.php @@ -0,0 +1,271 @@ + + * @author Aldo "xoen" Giambelluca + * @author Bart Visscher + * @author Brice Maron + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Frank Karlitschek + * @author Jakob Sack + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Schaffrath + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +/** + * This class is responsible for reading and writing config.php, the very basic + * configuration file of Nextcloud. + */ +class Config { + public const ENV_PREFIX = 'NC_'; + + /** @var array Associative array ($key => $value) */ + protected $cache = []; + /** @var string */ + protected $configDir; + /** @var string */ + protected $configFilePath; + /** @var string */ + protected $configFileName; + + /** + * @param string $configDir Path to the config dir, needs to end with '/' + * @param string $fileName (Optional) Name of the config file. Defaults to config.php + */ + public function __construct($configDir, $fileName = 'config.php') { + $this->configDir = $configDir; + $this->configFilePath = $this->configDir.$fileName; + $this->configFileName = $fileName; + $this->readData(); + } + + /** + * Lists all available config keys + * + * Please note that it does not return the values. + * + * @return array an array of key names + */ + public function getKeys() { + return array_keys($this->cache); + } + + /** + * Returns a config value + * + * gets its value from an `NC_` prefixed environment variable + * if it doesn't exist from config.php + * if this doesn't exist either, it will return the given `$default` + * + * @param string $key key + * @param mixed $default = null default value + * @return mixed the value or $default + */ + public function getValue($key, $default = null) { + $envValue = getenv(self::ENV_PREFIX . $key); + if ($envValue !== false) { + return $envValue; + } + + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + + return $default; + } + + /** + * Sets and deletes values and writes the config.php + * + * @param array $configs Associative array with `key => value` pairs + * If value is null, the config key will be deleted + */ + public function setValues(array $configs) { + $needsUpdate = false; + foreach ($configs as $key => $value) { + if ($value !== null) { + $needsUpdate |= $this->set($key, $value); + } else { + $needsUpdate |= $this->delete($key); + } + } + + if ($needsUpdate) { + // Write changes + $this->writeData(); + } + } + + /** + * Sets the value and writes it to config.php if required + * + * @param string $key key + * @param mixed $value value + */ + public function setValue($key, $value) { + if ($this->set($key, $value)) { + // Write changes + $this->writeData(); + } + } + + /** + * This function sets the value + * + * @param string $key key + * @param mixed $value value + * @return bool True if the file needs to be updated, false otherwise + */ + protected function set($key, $value) { + if (!isset($this->cache[$key]) || $this->cache[$key] !== $value) { + // Add change + $this->cache[$key] = $value; + return true; + } + + return false; + } + + /** + * Removes a key from the config and removes it from config.php if required + * @param string $key + */ + public function deleteKey($key) { + if ($this->delete($key)) { + // Write changes + $this->writeData(); + } + } + + /** + * This function removes a key from the config + * + * @param string $key + * @return bool True if the file needs to be updated, false otherwise + */ + protected function delete($key) { + if (isset($this->cache[$key])) { + // Delete key from cache + unset($this->cache[$key]); + return true; + } + return false; + } + + /** + * Loads the config file + * + * Reads the config file and saves it to the cache + * + * @throws \Exception If no lock could be acquired or the config file has not been found + */ + private function readData() { + // Default config should always get loaded + $configFiles = [$this->configFilePath]; + + // Add all files in the config dir ending with the same file name + $extra = glob($this->configDir.'*.'.$this->configFileName); + if (is_array($extra)) { + natsort($extra); + $configFiles = array_merge($configFiles, $extra); + } + + // Include file and merge config + foreach ($configFiles as $file) { + $fileExistsAndIsReadable = file_exists($file) && is_readable($file); + $filePointer = $fileExistsAndIsReadable ? fopen($file, 'r') : false; + if ($file === $this->configFilePath && + $filePointer === false) { + // Opening the main config might not be possible, e.g. if the wrong + // permissions are set (likely on a new installation) + continue; + } + + // Try to acquire a file lock + if (!flock($filePointer, LOCK_SH)) { + throw new \Exception(sprintf('Could not acquire a shared lock on the config file %s', $file)); + } + + unset($CONFIG); + include $file; + if (isset($CONFIG) && is_array($CONFIG)) { + $this->cache = array_merge($this->cache, $CONFIG); + } + + // Close the file pointer and release the lock + flock($filePointer, LOCK_UN); + fclose($filePointer); + } + } + + /** + * Writes the config file + * + * Saves the config to the config file. + * + * @throws HintException If the config file cannot be written to + * @throws \Exception If no file lock can be acquired + */ + private function writeData() { + // Create a php file ... + $content = "cache, true); + $content .= ";\n"; + + touch($this->configFilePath); + $filePointer = fopen($this->configFilePath, 'r+'); + + // Prevent others not to read the config + chmod($this->configFilePath, 0640); + + // File does not exist, this can happen when doing a fresh install + if (!is_resource($filePointer)) { + throw new HintException( + "Can't write into config directory!", + 'This can usually be fixed by giving the webserver write access to the config directory.'); + } + + // Try to acquire a file lock + if (!flock($filePointer, LOCK_EX)) { + throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath)); + } + + // Write the config and release the lock + ftruncate($filePointer, 0); + fwrite($filePointer, $content); + fflush($filePointer); + flock($filePointer, LOCK_UN); + fclose($filePointer); + + if (function_exists('opcache_invalidate')) { + @opcache_invalidate($this->configFilePath, true); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Console/Application.php b/docker/overlays/nextcloud/html/lib/private/Console/Application.php new file mode 100644 index 0000000..10c578f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Console/Application.php @@ -0,0 +1,233 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Weimann + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Console; + +use OC\MemoryInfo; +use OC\NeedsUpdateException; +use OC_App; +use OCP\AppFramework\QueryException; +use OCP\Console\ConsoleEvent; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IRequest; +use Symfony\Component\Console\Application as SymfonyApplication; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class Application { + /** @var IConfig */ + private $config; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** @var IRequest */ + private $request; + /** @var ILogger */ + private $logger; + /** @var MemoryInfo */ + private $memoryInfo; + + /** + * @param IConfig $config + * @param EventDispatcherInterface $dispatcher + * @param IRequest $request + * @param ILogger $logger + * @param MemoryInfo $memoryInfo + */ + public function __construct(IConfig $config, + EventDispatcherInterface $dispatcher, + IRequest $request, + ILogger $logger, + MemoryInfo $memoryInfo) { + $defaults = \OC::$server->getThemingDefaults(); + $this->config = $config; + $this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString()); + $this->dispatcher = $dispatcher; + $this->request = $request; + $this->logger = $logger; + $this->memoryInfo = $memoryInfo; + } + + /** + * @param InputInterface $input + * @param ConsoleOutputInterface $output + * @throws \Exception + */ + public function loadCommands( + InputInterface $input, + ConsoleOutputInterface $output + ) { + // $application is required to be defined in the register_command scripts + $application = $this->application; + $inputDefinition = $application->getDefinition(); + $inputDefinition->addOption( + new InputOption( + 'no-warnings', + null, + InputOption::VALUE_NONE, + 'Skip global warnings, show command output only', + null + ) + ); + try { + $input->bind($inputDefinition); + } catch (\RuntimeException $e) { + //expected if there are extra options + } + if ($input->getOption('no-warnings')) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + } + + if ($this->memoryInfo->isMemoryLimitSufficient() === false) { + $output->getErrorOutput()->writeln( + 'The current PHP memory limit ' . + 'is below the recommended value of 512MB.' + ); + } + + try { + require_once __DIR__ . '/../../../core/register_command.php'; + if ($this->config->getSystemValue('installed', false)) { + if (\OCP\Util::needUpgrade()) { + throw new NeedsUpdateException(); + } elseif ($this->config->getSystemValueBool('maintenance')) { + $this->writeMaintenanceModeInfo($input, $output); + } else { + OC_App::loadApps(); + foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if ($appPath === false) { + continue; + } + // load commands using info.xml + $info = \OC_App::getAppInfo($app); + if (isset($info['commands'])) { + $this->loadCommandsFromInfoXml($info['commands']); + } + // load from register_command.php + \OC_App::registerAutoloading($app, $appPath); + $file = $appPath . '/appinfo/register_command.php'; + if (file_exists($file)) { + try { + require $file; + } catch (\Exception $e) { + $this->logger->logException($e); + } + } + } + } + } elseif ($input->getArgument('command') !== '_completion' && $input->getArgument('command') !== 'maintenance:install') { + $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); + } + } catch (NeedsUpdateException $e) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); + $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); + } + } + + if ($input->getFirstArgument() !== 'check') { + $errors = \OC_Util::checkServer(\OC::$server->getSystemConfig()); + if (!empty($errors)) { + foreach ($errors as $error) { + $output->writeln((string)$error['error']); + $output->writeln((string)$error['hint']); + $output->writeln(''); + } + throw new \Exception("Environment not properly prepared."); + } + } + } + + /** + * Write a maintenance mode info. + * The commands "_completion" and "maintenance:mode" are excluded. + * + * @param InputInterface $input The input implementation for reading inputs. + * @param ConsoleOutputInterface $output The output implementation + * for writing outputs. + * @return void + */ + private function writeMaintenanceModeInfo( + InputInterface $input, ConsoleOutputInterface $output + ) { + if ($input->getArgument('command') !== '_completion' + && $input->getArgument('command') !== 'maintenance:mode') { + $errOutput = $output->getErrorOutput(); + $errOutput->writeln( + 'Nextcloud is in maintenance mode - ' . + 'no apps have been loaded' . PHP_EOL + ); + } + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) { + $this->application->setAutoExit($boolean); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + * @throws \Exception + */ + public function run(InputInterface $input = null, OutputInterface $output = null) { + $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( + ConsoleEvent::EVENT_RUN, + $this->request->server['argv'] + )); + return $this->application->run($input, $output); + } + + private function loadCommandsFromInfoXml($commands) { + foreach ($commands as $command) { + try { + $c = \OC::$server->query($command); + } catch (QueryException $e) { + if (class_exists($command)) { + $c = new $command(); + } else { + throw new \Exception("Console command '$command' is unknown and could not be loaded"); + } + } + + $this->application->add($c); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Console/TimestampFormatter.php b/docker/overlays/nextcloud/html/lib/private/Console/TimestampFormatter.php new file mode 100644 index 0000000..7f8250a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Console/TimestampFormatter.php @@ -0,0 +1,111 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Console; + +use OCP\IConfig; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface; + +class TimestampFormatter implements OutputFormatterInterface { + /** @var IConfig */ + protected $config; + + /** @var OutputFormatterInterface */ + protected $formatter; + + /** + * @param IConfig $config + * @param OutputFormatterInterface $formatter + */ + public function __construct(IConfig $config, OutputFormatterInterface $formatter) { + $this->config = $config; + $this->formatter = $formatter; + } + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages or not + */ + public function setDecorated($decorated) { + $this->formatter->setDecorated($decorated); + } + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated() { + return $this->formatter->isDecorated(); + } + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + */ + public function setStyle($name, OutputFormatterStyleInterface $style) { + $this->formatter->setStyle($name, $style); + } + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * @return bool + */ + public function hasStyle($name) { + return $this->formatter->hasStyle($name); + } + + /** + * Gets style options from style with specified name. + * + * @param string $name + * @return OutputFormatterStyleInterface + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle($name) { + return $this->formatter->getStyle($name); + } + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * @return string The styled message, prepended with a timestamp using the + * log timezone and dateformat, e.g. "2015-06-23T17:24:37+02:00" + */ + public function format($message) { + $timeZone = $this->config->getSystemValue('logtimezone', 'UTC'); + $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null; + + $time = new \DateTime('now', $timeZone); + $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTime::ATOM)); + + return $timestampInfo . ' ' . $this->formatter->format($message); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionFactory.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionFactory.php new file mode 100644 index 0000000..0cdd124 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionFactory.php @@ -0,0 +1,55 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu; + +use OC\Contacts\ContactsMenu\Actions\LinkAction; +use OCP\Contacts\ContactsMenu\IActionFactory; +use OCP\Contacts\ContactsMenu\ILinkAction; + +class ActionFactory implements IActionFactory { + + /** + * @param string $icon + * @param string $name + * @param string $href + * @return ILinkAction + */ + public function newLinkAction($icon, $name, $href) { + $action = new LinkAction(); + $action->setName($name); + $action->setIcon($icon); + $action->setHref($href); + return $action; + } + + /** + * @param string $icon + * @param string $name + * @param string $email + * @return ILinkAction + */ + public function newEMailAction($icon, $name, $email) { + return $this->newLinkAction($icon, $name, 'mailto:' . $email); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionProviderStore.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionProviderStore.php new file mode 100644 index 0000000..5513dd0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ActionProviderStore.php @@ -0,0 +1,112 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu; + +use Exception; +use OC\App\AppManager; +use OC\Contacts\ContactsMenu\Providers\EMailProvider; +use OCP\AppFramework\QueryException; +use OCP\Contacts\ContactsMenu\IProvider; +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\IUser; + +class ActionProviderStore { + + /** @var IServerContainer */ + private $serverContainer; + + /** @var AppManager */ + private $appManager; + + /** @var ILogger */ + private $logger; + + /** + * @param IServerContainer $serverContainer + * @param AppManager $appManager + * @param ILogger $logger + */ + public function __construct(IServerContainer $serverContainer, AppManager $appManager, ILogger $logger) { + $this->serverContainer = $serverContainer; + $this->appManager = $appManager; + $this->logger = $logger; + } + + /** + * @param IUser $user + * @return IProvider[] + * @throws Exception + */ + public function getProviders(IUser $user) { + $appClasses = $this->getAppProviderClasses($user); + $providerClasses = $this->getServerProviderClasses(); + $allClasses = array_merge($providerClasses, $appClasses); + $providers = []; + + foreach ($allClasses as $class) { + try { + $providers[] = $this->serverContainer->query($class); + } catch (QueryException $ex) { + $this->logger->logException($ex, [ + 'message' => "Could not load contacts menu action provider $class", + 'app' => 'core', + ]); + throw new Exception("Could not load contacts menu action provider"); + } + } + + return $providers; + } + + /** + * @return string[] + */ + private function getServerProviderClasses() { + return [ + EMailProvider::class, + ]; + } + + /** + * @param IUser $user + * @return string[] + */ + private function getAppProviderClasses(IUser $user) { + return array_reduce($this->appManager->getEnabledAppsForUser($user), function ($all, $appId) { + $info = $this->appManager->getAppInfo($appId); + + if (!isset($info['contactsmenu']) || !isset($info['contactsmenu'])) { + // Nothing to add + return $all; + } + + $providers = array_reduce($info['contactsmenu'], function ($all, $provider) { + return array_merge($all, [$provider]); + }, []); + + return array_merge($all, $providers); + }, []); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php new file mode 100644 index 0000000..eac169a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php @@ -0,0 +1,101 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu\Actions; + +use OCP\Contacts\ContactsMenu\ILinkAction; + +class LinkAction implements ILinkAction { + + /** @var string */ + private $icon; + + /** @var string */ + private $name; + + /** @var string */ + private $href; + + /** @var int */ + private $priority = 10; + + /** + * @param string $icon absolute URI to an icon + */ + public function setIcon($icon) { + $this->icon = $icon; + } + + /** + * @param string $name + */ + public function setName($name) { + $this->name = $name; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param int $priority + */ + public function setPriority($priority) { + $this->priority = $priority; + } + + /** + * @return int + */ + public function getPriority() { + return $this->priority; + } + + /** + * @param string $href + */ + public function setHref($href) { + $this->href = $href; + } + + /** + * @return string + */ + public function getHref() { + return $this->href; + } + + /** + * @return array + */ + public function jsonSerialize() { + return [ + 'title' => $this->name, + 'icon' => $this->icon, + 'hyperlink' => $this->href, + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ContactsStore.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ContactsStore.php new file mode 100644 index 0000000..e2bd7ed --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -0,0 +1,278 @@ + + * @copyright 2017 Lukas Reschke + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Georg Ehrke + * @author Julius Härtl + * @author Lukas Reschke + * @author Roeland Jago Douma + * @author Tobia De Koninck + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu; + +use OCP\Contacts\ContactsMenu\IContactsStore; +use OCP\Contacts\ContactsMenu\IEntry; +use OCP\Contacts\IManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; + +class ContactsStore implements IContactsStore { + + /** @var IManager */ + private $contactsManager; + + /** @var IConfig */ + private $config; + + /** @var IUserManager */ + private $userManager; + + /** @var IGroupManager */ + private $groupManager; + + /** + * @param IManager $contactsManager + * @param IConfig $config + * @param IUserManager $userManager + * @param IGroupManager $groupManager + */ + public function __construct(IManager $contactsManager, + IConfig $config, + IUserManager $userManager, + IGroupManager $groupManager) { + $this->contactsManager = $contactsManager; + $this->config = $config; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + } + + /** + * @param IUser $user + * @param string|null $filter + * @return IEntry[] + */ + public function getContacts(IUser $user, $filter, ?int $limit = null, ?int $offset = null) { + $options = []; + if ($limit !== null) { + $options['limit'] = $limit; + } + if ($offset !== null) { + $options['offset'] = $offset; + } + + $allContacts = $this->contactsManager->search( + $filter ?: '', + [ + 'FN', + 'EMAIL' + ], + $options + ); + + $entries = array_map(function (array $contact) { + return $this->contactArrayToEntry($contact); + }, $allContacts); + return $this->filterContacts( + $user, + $entries, + $filter + ); + } + + /** + * Filters the contacts. Applies 3 filters: + * 1. filter the current user + * 2. if the `shareapi_allow_share_dialog_user_enumeration` config option is + * enabled it will filter all local users + * 3. if the `shareapi_exclude_groups` config option is enabled and the + * current user is in an excluded group it will filter all local users. + * 4. if the `shareapi_only_share_with_group_members` config option is + * enabled it will filter all users which doens't have a common group + * with the current user. + * + * @param IUser $self + * @param Entry[] $entries + * @param string $filter + * @return Entry[] the filtered contacts + */ + private function filterContacts(IUser $self, + array $entries, + $filter) { + $disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes'; + $restrictEnumeration = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes'; + + // whether to filter out local users + $skipLocal = false; + // whether to filter out all users which doesn't have the same group as the current user + $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes' || $restrictEnumeration; + + $selfGroups = $this->groupManager->getUserGroupIds($self); + + if ($excludedGroups) { + $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $decodedExcludeGroups = json_decode($excludedGroups, true); + $excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : []; + + if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) { + // a group of the current user is excluded -> filter all local users + $skipLocal = true; + } + } + + $selfUID = $self->getUID(); + + return array_values(array_filter($entries, function (IEntry $entry) use ($self, $skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $filter) { + if ($skipLocal && $entry->getProperty('isLocalSystemBook') === true) { + return false; + } + + // Prevent enumerating local users + if ($disallowEnumeration && $entry->getProperty('isLocalSystemBook')) { + $filterUser = true; + + $mailAddresses = $entry->getEMailAddresses(); + foreach ($mailAddresses as $mailAddress) { + if ($mailAddress === $filter) { + $filterUser = false; + break; + } + } + + if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) { + $filterUser = false; + } + + if ($filterUser) { + return false; + } + } + + if ($ownGroupsOnly && $entry->getProperty('isLocalSystemBook') === true) { + $uid = $this->userManager->get($entry->getProperty('UID')); + + if ($uid === null) { + return false; + } + + $contactGroups = $this->groupManager->getUserGroupIds($uid); + if (count(array_intersect($contactGroups, $selfGroups)) === 0) { + // no groups in common, so shouldn't see the contact + return false; + } + } + + return $entry->getProperty('UID') !== $selfUID; + })); + } + + /** + * @param IUser $user + * @param integer $shareType + * @param string $shareWith + * @return IEntry|null + */ + public function findOne(IUser $user, $shareType, $shareWith) { + switch ($shareType) { + case 0: + case 6: + $filter = ['UID']; + break; + case 4: + $filter = ['EMAIL']; + break; + default: + return null; + } + + $userId = $user->getUID(); + $allContacts = $this->contactsManager->search($shareWith, $filter); + $contacts = array_filter($allContacts, function ($contact) use ($userId) { + return $contact['UID'] !== $userId; + }); + $match = null; + + foreach ($contacts as $contact) { + if ($shareType === 4 && isset($contact['EMAIL'])) { + if (in_array($shareWith, $contact['EMAIL'])) { + $match = $contact; + break; + } + } + if ($shareType === 0 || $shareType === 6) { + $isLocal = $contact['isLocalSystemBook'] ?? false; + if ($contact['UID'] === $shareWith && $isLocal === true) { + $match = $contact; + break; + } + } + } + + if ($match) { + $match = $this->filterContacts($user, [$this->contactArrayToEntry($match)], $shareWith); + if (count($match) === 1) { + $match = $match[0]; + } else { + $match = null; + } + } + + return $match; + } + + /** + * @param array $contact + * @return Entry + */ + private function contactArrayToEntry(array $contact) { + $entry = new Entry(); + + if (isset($contact['id'])) { + $entry->setId($contact['id']); + } + + if (isset($contact['FN'])) { + $entry->setFullName($contact['FN']); + } + + $avatarPrefix = "VALUE=uri:"; + if (isset($contact['PHOTO']) && strpos($contact['PHOTO'], $avatarPrefix) === 0) { + $entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix))); + } + + if (isset($contact['EMAIL'])) { + foreach ($contact['EMAIL'] as $email) { + $entry->addEMailAddress($email); + } + } + + // Attach all other properties to the entry too because some + // providers might make use of it. + $entry->setProperties($contact); + + return $entry; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Entry.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Entry.php new file mode 100644 index 0000000..675d925 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Entry.php @@ -0,0 +1,167 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu; + +use OCP\Contacts\ContactsMenu\IAction; +use OCP\Contacts\ContactsMenu\IEntry; + +class Entry implements IEntry { + + /** @var string|int|null */ + private $id = null; + + /** @var string */ + private $fullName = ''; + + /** @var string[] */ + private $emailAddresses = []; + + /** @var string|null */ + private $avatar; + + /** @var IAction[] */ + private $actions = []; + + /** @var array */ + private $properties = []; + + /** + * @param string $id + */ + public function setId($id) { + $this->id = $id; + } + + /** + * @param string $displayName + */ + public function setFullName($displayName) { + $this->fullName = $displayName; + } + + /** + * @return string + */ + public function getFullName() { + return $this->fullName; + } + + /** + * @param string $address + */ + public function addEMailAddress($address) { + $this->emailAddresses[] = $address; + } + + /** + * @return string + */ + public function getEMailAddresses() { + return $this->emailAddresses; + } + + /** + * @param string $avatar + */ + public function setAvatar($avatar) { + $this->avatar = $avatar; + } + + /** + * @return string + */ + public function getAvatar() { + return $this->avatar; + } + + /** + * @param IAction $action + */ + public function addAction(IAction $action) { + $this->actions[] = $action; + $this->sortActions(); + } + + /** + * @return IAction[] + */ + public function getActions() { + return $this->actions; + } + + /** + * sort the actions by priority and name + */ + private function sortActions() { + usort($this->actions, function (IAction $action1, IAction $action2) { + $prio1 = $action1->getPriority(); + $prio2 = $action2->getPriority(); + + if ($prio1 === $prio2) { + // Ascending order for same priority + return strcasecmp($action1->getName(), $action2->getName()); + } + + // Descending order when priority differs + return $prio2 - $prio1; + }); + } + + /** + * @param array $contact key-value array containing additional properties + */ + public function setProperties(array $contact) { + $this->properties = $contact; + } + + /** + * @param string $key + * @return mixed + */ + public function getProperty($key) { + if (!isset($this->properties[$key])) { + return null; + } + return $this->properties[$key]; + } + + /** + * @return array + */ + public function jsonSerialize() { + $topAction = !empty($this->actions) ? $this->actions[0]->jsonSerialize() : null; + $otherActions = array_map(function (IAction $action) { + return $action->jsonSerialize(); + }, array_slice($this->actions, 1)); + + return [ + 'id' => $this->id, + 'fullName' => $this->fullName, + 'avatar' => $this->getAvatar(), + 'topAction' => $topAction, + 'actions' => $otherActions, + 'lastMessage' => '', + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Manager.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Manager.php new file mode 100644 index 0000000..8f772b8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Manager.php @@ -0,0 +1,122 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Georg Ehrke + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu; + +use OCP\App\IAppManager; +use OCP\Contacts\ContactsMenu\IEntry; +use OCP\IConfig; +use OCP\IUser; + +class Manager { + + /** @var ContactsStore */ + private $store; + + /** @var ActionProviderStore */ + private $actionProviderStore; + + /** @var IAppManager */ + private $appManager; + + /** @var IConfig */ + private $config; + + /** + * @param ContactsStore $store + * @param ActionProviderStore $actionProviderStore + * @param IAppManager $appManager + */ + public function __construct(ContactsStore $store, ActionProviderStore $actionProviderStore, IAppManager $appManager, IConfig $config) { + $this->store = $store; + $this->actionProviderStore = $actionProviderStore; + $this->appManager = $appManager; + $this->config = $config; + } + + /** + * @param IUser $user + * @param string $filter + * @return array + */ + public function getEntries(IUser $user, $filter) { + $maxAutocompleteResults = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', 25); + $minSearchStringLength = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0); + $topEntries = []; + if (strlen($filter) >= $minSearchStringLength) { + $entries = $this->store->getContacts($user, $filter, $maxAutocompleteResults); + + $sortedEntries = $this->sortEntries($entries); + $topEntries = array_slice($sortedEntries, 0, $maxAutocompleteResults); + $this->processEntries($topEntries, $user); + } + + $contactsEnabled = $this->appManager->isEnabledForUser('contacts', $user); + return [ + 'contacts' => $topEntries, + 'contactsAppEnabled' => $contactsEnabled, + ]; + } + + /** + * @param IUser $user + * @param integer $shareType + * @param string $shareWith + * @return IEntry + */ + public function findOne(IUser $user, $shareType, $shareWith) { + $entry = $this->store->findOne($user, $shareType, $shareWith); + if ($entry) { + $this->processEntries([$entry], $user); + } + + return $entry; + } + + /** + * @param IEntry[] $entries + * @return IEntry[] + */ + private function sortEntries(array $entries) { + usort($entries, function (IEntry $entryA, IEntry $entryB) { + return strcasecmp($entryA->getFullName(), $entryB->getFullName()); + }); + return $entries; + } + + /** + * @param IEntry[] $entries + * @param IUser $user + */ + private function processEntries(array $entries, IUser $user) { + $providers = $this->actionProviderStore->getProviders($user); + foreach ($entries as $entry) { + foreach ($providers as $provider) { + $provider->process($entry); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php new file mode 100644 index 0000000..bb5e64d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php @@ -0,0 +1,62 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu\Providers; + +use OCP\Contacts\ContactsMenu\IActionFactory; +use OCP\Contacts\ContactsMenu\IEntry; +use OCP\Contacts\ContactsMenu\IProvider; +use OCP\IURLGenerator; + +class EMailProvider implements IProvider { + + /** @var IActionFactory */ + private $actionFactory; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param IActionFactory $actionFactory + * @param IURLGenerator $urlGenerator + */ + public function __construct(IActionFactory $actionFactory, IURLGenerator $urlGenerator) { + $this->actionFactory = $actionFactory; + $this->urlGenerator = $urlGenerator; + } + + /** + * @param IEntry $entry + */ + public function process(IEntry $entry) { + $iconUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mail.svg')); + foreach ($entry->getEMailAddresses() as $address) { + if (empty($address)) { + // Skip + continue; + } + $action = $this->actionFactory->newEMailAction($iconUrl, $address, $address); + $entry->addAction($action); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/ContactsManager.php b/docker/overlays/nextcloud/html/lib/private/ContactsManager.php new file mode 100644 index 0000000..4cdc3d4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/ContactsManager.php @@ -0,0 +1,209 @@ + + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Morris Jobke + * @author Thomas Müller + * @author Tobia De Koninck + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\Constants; +use OCP\Contacts\IManager; +use OCP\IAddressBook; + +class ContactsManager implements IManager { + + /** + * This function is used to search and find contacts within the users address books. + * In case $pattern is empty all contacts will be returned. + * + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options = array() to define the search behavior + * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results + * @return array an array of contacts which are arrays of key-value-pairs + */ + public function search($pattern, $searchProperties = [], $options = []) { + $this->loadAddressBooks(); + $result = []; + foreach ($this->addressBooks as $addressBook) { + $r = $addressBook->search($pattern, $searchProperties, $options); + $contacts = []; + foreach ($r as $c) { + $c['addressbook-key'] = $addressBook->getKey(); + $contacts[] = $c; + } + $result = array_merge($result, $contacts); + } + + return $result; + } + + /** + * This function can be used to delete the contact identified by the given id + * + * @param object $id the unique identifier to a contact + * @param string $address_book_key identifier of the address book in which the contact shall be deleted + * @return bool successful or not + */ + public function delete($id, $address_book_key) { + $addressBook = $this->getAddressBook($address_book_key); + if (!$addressBook) { + return null; + } + + if ($addressBook->getPermissions() & Constants::PERMISSION_DELETE) { + return $addressBook->delete($id); + } + + return null; + } + + /** + * This function is used to create a new contact if 'id' is not given or not present. + * Otherwise the contact will be updated by replacing the entire data set. + * + * @param array $properties this array if key-value-pairs defines a contact + * @param string $address_book_key identifier of the address book in which the contact shall be created or updated + * @return array representing the contact just created or updated + */ + public function createOrUpdate($properties, $address_book_key) { + $addressBook = $this->getAddressBook($address_book_key); + if (!$addressBook) { + return null; + } + + if ($addressBook->getPermissions() & Constants::PERMISSION_CREATE) { + return $addressBook->createOrUpdate($properties); + } + + return null; + } + + /** + * Check if contacts are available (e.g. contacts app enabled) + * + * @return bool true if enabled, false if not + */ + public function isEnabled() { + return !empty($this->addressBooks) || !empty($this->addressBookLoaders); + } + + /** + * @param IAddressBook $addressBook + */ + public function registerAddressBook(IAddressBook $addressBook) { + $this->addressBooks[$addressBook->getKey()] = $addressBook; + } + + /** + * @param IAddressBook $addressBook + */ + public function unregisterAddressBook(IAddressBook $addressBook) { + unset($this->addressBooks[$addressBook->getKey()]); + } + + /** + * Return a list of the user's addressbooks display names + * ! The addressBook displayName are not unique, please use getUserAddressBooks + * + * @return IAddressBook[] + * @since 6.0.0 + * @deprecated 16.0.0 - Use `$this->getUserAddressBooks()` instead + */ + public function getAddressBooks() { + $this->loadAddressBooks(); + $result = []; + foreach ($this->addressBooks as $addressBook) { + $result[$addressBook->getKey()] = $addressBook->getDisplayName(); + } + + return $result; + } + + /** + * Return a list of the user's addressbooks + * + * @return IAddressBook[] + * @since 16.0.0 + */ + public function getUserAddressBooks(): array { + $this->loadAddressBooks(); + return $this->addressBooks; + } + + /** + * removes all registered address book instances + */ + public function clear() { + $this->addressBooks = []; + $this->addressBookLoaders = []; + } + + /** + * @var IAddressBook[] which holds all registered address books + */ + private $addressBooks = []; + + /** + * @var \Closure[] to call to load/register address books + */ + private $addressBookLoaders = []; + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * address books are actually requested + * + * @param \Closure $callable + */ + public function register(\Closure $callable) { + $this->addressBookLoaders[] = $callable; + } + + /** + * Get (and load when needed) the address book for $key + * + * @param string $addressBookKey + * @return IAddressBook + */ + protected function getAddressBook($addressBookKey) { + $this->loadAddressBooks(); + if (!array_key_exists($addressBookKey, $this->addressBooks)) { + return null; + } + + return $this->addressBooks[$addressBookKey]; + } + + /** + * Load all address books registered with 'register' + */ + protected function loadAddressBooks() { + foreach ($this->addressBookLoaders as $callable) { + $callable($this); + } + $this->addressBookLoaders = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/Adapter.php b/docker/overlays/nextcloud/html/lib/private/DB/Adapter.php new file mode 100644 index 0000000..562fea2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/Adapter.php @@ -0,0 +1,145 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Jonny007-MKD <1-23-4-5@web.de> + * @author Morris Jobke + * @author Ole Ostergaard + * @author Ole Ostergaard + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; + +/** + * This handles the way we use to write queries, into something that can be + * handled by the database abstraction layer. + */ +class Adapter { + + /** + * @var \OC\DB\Connection $conn + */ + protected $conn; + + public function __construct($conn) { + $this->conn = $conn; + } + + /** + * @param string $table name + * @return int id of last insert statement + */ + public function lastInsertId($table) { + return $this->conn->realLastInsertId($table); + } + + /** + * @param string $statement that needs to be changed so the db can handle it + * @return string changed statement + */ + public function fixupStatement($statement) { + return $statement; + } + + /** + * Create an exclusive read+write lock on a table + * + * @param string $tableName + * @since 9.1.0 + */ + public function lockTable($tableName) { + $this->conn->beginTransaction(); + $this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE'); + } + + /** + * Release a previous acquired lock again + * + * @since 9.1.0 + */ + public function unlockTable() { + $this->conn->commit(); + } + + /** + * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance + * it is needed that there is also a unique constraint on the values. Then this method will + * catch the exception and return 0. + * + * @param string $table The table name (will replace *PREFIX* with the actual prefix) + * @param array $input data that should be inserted into the table (column name => value) + * @param array|null $compare List of values that should be checked for "if not exists" + * If this is null or an empty array, all keys of $input will be compared + * Please note: text fields (clob) must not be used in the compare array + * @return int number of inserted rows + * @throws \Doctrine\DBAL\DBALException + * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371 + */ + public function insertIfNotExist($table, $input, array $compare = null) { + if (empty($compare)) { + $compare = array_keys($input); + } + $query = 'INSERT INTO `' .$table . '` (`' + . implode('`,`', array_keys($input)) . '`) SELECT ' + . str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative? + . 'FROM `' . $table . '` WHERE '; + + $inserts = array_values($input); + foreach ($compare as $key) { + $query .= '`' . $key . '`'; + if (is_null($input[$key])) { + $query .= ' IS NULL AND '; + } else { + $inserts[] = $input[$key]; + $query .= ' = ? AND '; + } + } + $query = substr($query, 0, -5); + $query .= ' HAVING COUNT(*) = 0'; + + try { + return $this->conn->executeUpdate($query, $inserts); + } catch (UniqueConstraintViolationException $e) { + // if this is thrown then a concurrent insert happened between the insert and the sub-select in the insert, that should have avoided it + // it's fine to ignore this then + // + // more discussions about this can be found at https://github.com/nextcloud/server/pull/12315 + return 0; + } + } + + public function insertIgnoreConflict(string $table,array $values) : int { + try { + $builder = $this->conn->getQueryBuilder(); + $builder->insert($table); + foreach ($values as $key => $value) { + $builder->setValue($key, $builder->createNamedParameter($value)); + } + return $builder->execute(); + } catch (UniqueConstraintViolationException $e) { + return 0; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/AdapterMySQL.php b/docker/overlays/nextcloud/html/lib/private/DB/AdapterMySQL.php new file mode 100644 index 0000000..c925f93 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/AdapterMySQL.php @@ -0,0 +1,56 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class AdapterMySQL extends Adapter { + + /** @var string */ + protected $charset; + + /** + * @param string $tableName + */ + public function lockTable($tableName) { + $this->conn->executeUpdate('LOCK TABLES `' .$tableName . '` WRITE'); + } + + public function unlockTable() { + $this->conn->executeUpdate('UNLOCK TABLES'); + } + + public function fixupStatement($statement) { + $statement = str_replace(' ILIKE ', ' COLLATE ' . $this->getCharset() . '_general_ci LIKE ', $statement); + return $statement; + } + + protected function getCharset() { + if (!$this->charset) { + $params = $this->conn->getParams(); + $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + } + + return $this->charset; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/AdapterOCI8.php b/docker/overlays/nextcloud/html/lib/private/DB/AdapterOCI8.php new file mode 100644 index 0000000..5539456 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/AdapterOCI8.php @@ -0,0 +1,50 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class AdapterOCI8 extends Adapter { + public function lastInsertId($table) { + if (is_null($table)) { + throw new \InvalidArgumentException('Oracle requires a table name to be passed into lastInsertId()'); + } + if ($table !== null) { + $suffix = '_SEQ'; + $table = '"' . $table . $suffix . '"'; + } + return $this->conn->realLastInsertId($table); + } + + public const UNIX_TIMESTAMP_REPLACEMENT = "(cast(sys_extract_utc(systimestamp) as date) - date'1970-01-01') * 86400"; + + public function fixupStatement($statement) { + $statement = preg_replace('/`(\w+)` ILIKE \?/', 'REGEXP_LIKE(`$1`, \'^\' || REPLACE(?, \'%\', \'.*\') || \'$\', \'i\')', $statement); + $statement = str_replace('`', '"', $statement); + $statement = str_ireplace('NOW()', 'CURRENT_TIMESTAMP', $statement); + $statement = str_ireplace('UNIX_TIMESTAMP()', self::UNIX_TIMESTAMP_REPLACEMENT, $statement); + return $statement; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/AdapterPgSql.php b/docker/overlays/nextcloud/html/lib/private/DB/AdapterPgSql.php new file mode 100644 index 0000000..77f0b6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/AdapterPgSql.php @@ -0,0 +1,70 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Morris Jobke + * @author Ole Ostergaard + * @author Ole Ostergaard + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class AdapterPgSql extends Adapter { + protected $compatModePre9_5 = null; + + public function lastInsertId($table) { + return $this->conn->fetchColumn('SELECT lastval()'); + } + + public const UNIX_TIMESTAMP_REPLACEMENT = 'cast(extract(epoch from current_timestamp) as integer)'; + public function fixupStatement($statement) { + $statement = str_replace('`', '"', $statement); + $statement = str_ireplace('UNIX_TIMESTAMP()', self::UNIX_TIMESTAMP_REPLACEMENT, $statement); + return $statement; + } + + public function insertIgnoreConflict(string $table,array $values) : int { + if ($this->isPre9_5CompatMode() === true) { + return parent::insertIgnoreConflict($table, $values); + } + + // "upsert" is only available since PgSQL 9.5, but the generic way + // would leave error logs in the DB. + $builder = $this->conn->getQueryBuilder(); + $builder->insert($table); + foreach ($values as $key => $value) { + $builder->setValue($key, $builder->createNamedParameter($value)); + } + $queryString = $builder->getSQL() . ' ON CONFLICT DO NOTHING'; + return $this->conn->executeUpdate($queryString, $builder->getParameters(), $builder->getParameterTypes()); + } + + protected function isPre9_5CompatMode(): bool { + if ($this->compatModePre9_5 !== null) { + return $this->compatModePre9_5; + } + + $version = $this->conn->fetchColumn('SHOW SERVER_VERSION'); + $this->compatModePre9_5 = version_compare($version, '9.5', '<'); + + return $this->compatModePre9_5; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/AdapterSqlite.php b/docker/overlays/nextcloud/html/lib/private/DB/AdapterSqlite.php new file mode 100644 index 0000000..43ec4a9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/AdapterSqlite.php @@ -0,0 +1,100 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; + +class AdapterSqlite extends Adapter { + + /** + * @param string $tableName + */ + public function lockTable($tableName) { + $this->conn->executeUpdate('BEGIN EXCLUSIVE TRANSACTION'); + } + + public function unlockTable() { + $this->conn->executeUpdate('COMMIT TRANSACTION'); + } + + public function fixupStatement($statement) { + $statement = preg_replace('/`(\w+)` ILIKE \?/', 'LOWER($1) LIKE LOWER(?)', $statement); + $statement = str_replace('`', '"', $statement); + $statement = str_ireplace('NOW()', 'datetime(\'now\')', $statement); + $statement = str_ireplace('GREATEST(', 'MAX(', $statement); + $statement = str_ireplace('UNIX_TIMESTAMP()', 'strftime(\'%s\',\'now\')', $statement); + return $statement; + } + + /** + * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance + * it is needed that there is also a unique constraint on the values. Then this method will + * catch the exception and return 0. + * + * @param string $table The table name (will replace *PREFIX* with the actual prefix) + * @param array $input data that should be inserted into the table (column name => value) + * @param array|null $compare List of values that should be checked for "if not exists" + * If this is null or an empty array, all keys of $input will be compared + * Please note: text fields (clob) must not be used in the compare array + * @return int number of inserted rows + * @throws \Doctrine\DBAL\DBALException + * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371 + */ + public function insertIfNotExist($table, $input, array $compare = null) { + if (empty($compare)) { + $compare = array_keys($input); + } + $fieldList = '`' . implode('`,`', array_keys($input)) . '`'; + $query = "INSERT INTO `$table` ($fieldList) SELECT " + . str_repeat('?,', count($input)-1).'? ' + . " WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE "; + + $inserts = array_values($input); + foreach ($compare as $key) { + $query .= '`' . $key . '`'; + if (is_null($input[$key])) { + $query .= ' IS NULL AND '; + } else { + $inserts[] = $input[$key]; + $query .= ' = ? AND '; + } + } + $query = substr($query, 0, -5); + $query .= ')'; + + try { + return $this->conn->executeUpdate($query, $inserts); + } catch (UniqueConstraintViolationException $e) { + // if this is thrown then a concurrent insert happened between the insert and the sub-select in the insert, that should have avoided it + // it's fine to ignore this then + // + // more discussions about this can be found at https://github.com/nextcloud/server/pull/12315 + return 0; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/Connection.php b/docker/overlays/nextcloud/html/lib/private/DB/Connection.php new file mode 100644 index 0000000..c5766dc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/Connection.php @@ -0,0 +1,452 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Ole Ostergaard + * @author Ole Ostergaard + * @author Philipp Schaffrath + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception\ConstraintViolationException; +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Schema\Schema; +use OC\DB\QueryBuilder\QueryBuilder; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\PreConditionNotMetException; + +class Connection extends ReconnectWrapper implements IDBConnection { + /** + * @var string $tablePrefix + */ + protected $tablePrefix; + + /** + * @var \OC\DB\Adapter $adapter + */ + protected $adapter; + + protected $lockedTable = null; + + public function connect() { + try { + return parent::connect(); + } catch (DBALException $e) { + // throw a new exception to prevent leaking info from the stacktrace + throw new DBALException('Failed to connect to the database: ' . $e->getMessage(), $e->getCode()); + } + } + + /** + * Returns a QueryBuilder for the connection. + * + * @return \OCP\DB\QueryBuilder\IQueryBuilder + */ + public function getQueryBuilder() { + return new QueryBuilder( + $this, + \OC::$server->getSystemConfig(), + \OC::$server->getLogger() + ); + } + + /** + * Gets the QueryBuilder for the connection. + * + * @return \Doctrine\DBAL\Query\QueryBuilder + * @deprecated please use $this->getQueryBuilder() instead + */ + public function createQueryBuilder() { + $backtrace = $this->getCallerBacktrace(); + \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + return parent::createQueryBuilder(); + } + + /** + * Gets the ExpressionBuilder for the connection. + * + * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder + * @deprecated please use $this->getQueryBuilder()->expr() instead + */ + public function getExpressionBuilder() { + $backtrace = $this->getCallerBacktrace(); + \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + return parent::getExpressionBuilder(); + } + + /** + * Get the file and line that called the method where `getCallerBacktrace()` was used + * + * @return string + */ + protected function getCallerBacktrace() { + $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // 0 is the method where we use `getCallerBacktrace` + // 1 is the target method which uses the method we want to log + if (isset($traces[1])) { + return $traces[1]['file'] . ':' . $traces[1]['line']; + } + + return ''; + } + + /** + * @return string + */ + public function getPrefix() { + return $this->tablePrefix; + } + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * @throws \Exception + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) { + if (!isset($params['adapter'])) { + throw new \Exception('adapter not set'); + } + if (!isset($params['tablePrefix'])) { + throw new \Exception('tablePrefix not set'); + } + parent::__construct($params, $driver, $config, $eventManager); + $this->adapter = new $params['adapter']($this); + $this->tablePrefix = $params['tablePrefix']; + } + + /** + * Prepares an SQL statement. + * + * @param string $statement The SQL statement to prepare. + * @param int $limit + * @param int $offset + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + */ + public function prepare($statement, $limit=null, $offset=null) { + if ($limit === -1) { + $limit = null; + } + if (!is_null($limit)) { + $platform = $this->getDatabasePlatform(); + $statement = $platform->modifyLimitQuery($statement, $limit, $offset); + } + $statement = $this->replaceTablePrefix($statement); + $statement = $this->adapter->fixupStatement($statement); + + return parent::prepare($statement); + } + + /** + * Executes an, optionally parametrized, SQL query. + * + * If the query is parametrized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp The query cache profile, optional. + * + * @return \Doctrine\DBAL\Driver\Statement The executed statement. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeQuery($query, $params, $types, $qcp); + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * + * @return integer The number of affected rows. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function executeUpdate($query, array $params = [], array $types = []) { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeUpdate($query, $params, $types); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string $seqName Name of the sequence object from which the ID should be returned. + * @return string A string representation of the last inserted ID. + */ + public function lastInsertId($seqName = null) { + if ($seqName) { + $seqName = $this->replaceTablePrefix($seqName); + } + return $this->adapter->lastInsertId($seqName); + } + + // internal use + public function realLastInsertId($seqName = null) { + return parent::lastInsertId($seqName); + } + + /** + * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance + * it is needed that there is also a unique constraint on the values. Then this method will + * catch the exception and return 0. + * + * @param string $table The table name (will replace *PREFIX* with the actual prefix) + * @param array $input data that should be inserted into the table (column name => value) + * @param array|null $compare List of values that should be checked for "if not exists" + * If this is null or an empty array, all keys of $input will be compared + * Please note: text fields (clob) must not be used in the compare array + * @return int number of inserted rows + * @throws \Doctrine\DBAL\DBALException + * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371 + */ + public function insertIfNotExist($table, $input, array $compare = null) { + return $this->adapter->insertIfNotExist($table, $input, $compare); + } + + public function insertIgnoreConflict(string $table, array $values) : int { + return $this->adapter->insertIgnoreConflict($table, $values); + } + + private function getType($value) { + if (is_bool($value)) { + return IQueryBuilder::PARAM_BOOL; + } elseif (is_int($value)) { + return IQueryBuilder::PARAM_INT; + } else { + return IQueryBuilder::PARAM_STR; + } + } + + /** + * Insert or update a row value + * + * @param string $table + * @param array $keys (column name => value) + * @param array $values (column name => value) + * @param array $updatePreconditionValues ensure values match preconditions (column name => value) + * @return int number of new rows + * @throws \Doctrine\DBAL\DBALException + * @throws PreConditionNotMetException + */ + public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) { + try { + $insertQb = $this->getQueryBuilder(); + $insertQb->insert($table) + ->values( + array_map(function ($value) use ($insertQb) { + return $insertQb->createNamedParameter($value, $this->getType($value)); + }, array_merge($keys, $values)) + ); + return $insertQb->execute(); + } catch (ConstraintViolationException $e) { + // value already exists, try update + $updateQb = $this->getQueryBuilder(); + $updateQb->update($table); + foreach ($values as $name => $value) { + $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value))); + } + $where = $updateQb->expr()->andX(); + $whereValues = array_merge($keys, $updatePreconditionValues); + foreach ($whereValues as $name => $value) { + $where->add($updateQb->expr()->eq( + $name, + $updateQb->createNamedParameter($value, $this->getType($value)), + $this->getType($value) + )); + } + $updateQb->where($where); + $affected = $updateQb->execute(); + + if ($affected === 0 && !empty($updatePreconditionValues)) { + throw new PreConditionNotMetException(); + } + + return 0; + } + } + + /** + * Create an exclusive read+write lock on a table + * + * @param string $tableName + * @throws \BadMethodCallException When trying to acquire a second lock + * @since 9.1.0 + */ + public function lockTable($tableName) { + if ($this->lockedTable !== null) { + throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.'); + } + + $tableName = $this->tablePrefix . $tableName; + $this->lockedTable = $tableName; + $this->adapter->lockTable($tableName); + } + + /** + * Release a previous acquired lock again + * + * @since 9.1.0 + */ + public function unlockTable() { + $this->adapter->unlockTable(); + $this->lockedTable = null; + } + + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @return string + */ + public function getError() { + $msg = $this->errorCode() . ': '; + $errorInfo = $this->errorInfo(); + if (is_array($errorInfo)) { + $msg .= 'SQLSTATE = '.$errorInfo[0] . ', '; + $msg .= 'Driver Code = '.$errorInfo[1] . ', '; + $msg .= 'Driver Message = '.$errorInfo[2]; + } + return $msg; + } + + /** + * Drop a table from the database if it exists + * + * @param string $table table name without the prefix + */ + public function dropTable($table) { + $table = $this->tablePrefix . trim($table); + $schema = $this->getSchemaManager(); + if ($schema->tablesExist([$table])) { + $schema->dropTable($table); + } + } + + /** + * Check if a table exists + * + * @param string $table table name without the prefix + * @return bool + */ + public function tableExists($table) { + $table = $this->tablePrefix . trim($table); + $schema = $this->getSchemaManager(); + return $schema->tablesExist([$table]); + } + + // internal use + /** + * @param string $statement + * @return string + */ + protected function replaceTablePrefix($statement) { + return str_replace('*PREFIX*', $this->tablePrefix, $statement); + } + + /** + * Check if a transaction is active + * + * @return bool + * @since 8.2.0 + */ + public function inTransaction() { + return $this->getTransactionNestingLevel() > 0; + } + + /** + * Escape a parameter to be used in a LIKE query + * + * @param string $param + * @return string + */ + public function escapeLikeParameter($param) { + return addcslashes($param, '\\_%'); + } + + /** + * Check whether or not the current database support 4byte wide unicode + * + * @return bool + * @since 11.0.0 + */ + public function supports4ByteText() { + if (!$this->getDatabasePlatform() instanceof MySqlPlatform) { + return true; + } + return $this->getParams()['charset'] === 'utf8mb4'; + } + + + /** + * Create the schema of the connected database + * + * @return Schema + */ + public function createSchema() { + $schemaManager = new MDB2SchemaManager($this); + $migrator = $schemaManager->getMigrator(); + return $migrator->createSchema(); + } + + /** + * Migrate the database to the given schema + * + * @param Schema $toSchema + */ + public function migrateToSchema(Schema $toSchema) { + $schemaManager = new MDB2SchemaManager($this); + $migrator = $schemaManager->getMigrator(); + $migrator->migrate($toSchema); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/ConnectionFactory.php b/docker/overlays/nextcloud/html/lib/private/DB/ConnectionFactory.php new file mode 100644 index 0000000..441e456 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/ConnectionFactory.php @@ -0,0 +1,258 @@ + + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Event\Listeners\OracleSessionInit; +use Doctrine\DBAL\Event\Listeners\SQLSessionInit; +use OC\SystemConfig; + +/** + * Takes care of creating and configuring Doctrine connections. + */ +class ConnectionFactory { + /** @var string default database name */ + public const DEFAULT_DBNAME = 'owncloud'; + + /** @var string default database table prefix */ + public const DEFAULT_DBTABLEPREFIX = 'oc_'; + + /** + * @var array + * + * Array mapping DBMS type to default connection parameters passed to + * \Doctrine\DBAL\DriverManager::getConnection(). + */ + protected $defaultConnectionParams = [ + 'mysql' => [ + 'adapter' => AdapterMySQL::class, + 'charset' => 'UTF8', + 'driver' => 'pdo_mysql', + 'wrapperClass' => Connection::class, + ], + 'oci' => [ + 'adapter' => AdapterOCI8::class, + 'charset' => 'AL32UTF8', + 'driver' => 'oci8', + 'wrapperClass' => OracleConnection::class, + ], + 'pgsql' => [ + 'adapter' => AdapterPgSql::class, + 'driver' => 'pdo_pgsql', + 'wrapperClass' => Connection::class, + ], + 'sqlite3' => [ + 'adapter' => AdapterSqlite::class, + 'driver' => 'pdo_sqlite', + 'wrapperClass' => Connection::class, + ], + ]; + + /** @var SystemConfig */ + private $config; + + /** + * ConnectionFactory constructor. + * + * @param SystemConfig $systemConfig + */ + public function __construct(SystemConfig $systemConfig) { + $this->config = $systemConfig; + if ($this->config->getValue('mysql.utf8mb4', false)) { + $this->defaultConnectionParams['mysql']['charset'] = 'utf8mb4'; + } + } + + /** + * @brief Get default connection parameters for a given DBMS. + * @param string $type DBMS type + * @throws \InvalidArgumentException If $type is invalid + * @return array Default connection parameters. + */ + public function getDefaultConnectionParams($type) { + $normalizedType = $this->normalizeType($type); + if (!isset($this->defaultConnectionParams[$normalizedType])) { + throw new \InvalidArgumentException("Unsupported type: $type"); + } + $result = $this->defaultConnectionParams[$normalizedType]; + // \PDO::MYSQL_ATTR_FOUND_ROWS may not be defined, e.g. when the MySQL + // driver is missing. In this case, we won't be able to connect anyway. + if ($normalizedType === 'mysql' && defined('\PDO::MYSQL_ATTR_FOUND_ROWS')) { + $result['driverOptions'] = [ + \PDO::MYSQL_ATTR_FOUND_ROWS => true, + ]; + } + return $result; + } + + /** + * @brief Get default connection parameters for a given DBMS. + * @param string $type DBMS type + * @param array $additionalConnectionParams Additional connection parameters + * @return \OC\DB\Connection + */ + public function getConnection($type, $additionalConnectionParams) { + $normalizedType = $this->normalizeType($type); + $eventManager = new EventManager(); + $eventManager->addEventSubscriber(new SetTransactionIsolationLevel()); + switch ($normalizedType) { + case 'mysql': + $eventManager->addEventSubscriber( + new SQLSessionInit("SET SESSION AUTOCOMMIT=1")); + break; + case 'oci': + $eventManager->addEventSubscriber(new OracleSessionInit); + // the driverOptions are unused in dbal and need to be mapped to the parameters + if (isset($additionalConnectionParams['driverOptions'])) { + $additionalConnectionParams = array_merge($additionalConnectionParams, $additionalConnectionParams['driverOptions']); + } + $host = $additionalConnectionParams['host']; + $port = isset($additionalConnectionParams['port']) ? $additionalConnectionParams['port'] : null; + $dbName = $additionalConnectionParams['dbname']; + + // we set the connect string as dbname and unset the host to coerce doctrine into using it as connect string + if ($host === '') { + $additionalConnectionParams['dbname'] = $dbName; // use dbname as easy connect name + } else { + $additionalConnectionParams['dbname'] = '//' . $host . (!empty($port) ? ":{$port}" : "") . '/' . $dbName; + } + unset($additionalConnectionParams['host']); + break; + + case 'sqlite3': + $journalMode = $additionalConnectionParams['sqlite.journal_mode']; + $additionalConnectionParams['platform'] = new OCSqlitePlatform(); + $eventManager->addEventSubscriber(new SQLiteSessionInit(true, $journalMode)); + break; + } + /** @var Connection $connection */ + $connection = DriverManager::getConnection( + array_merge($this->getDefaultConnectionParams($type), $additionalConnectionParams), + new Configuration(), + $eventManager + ); + return $connection; + } + + /** + * @brief Normalize DBMS type + * @param string $type DBMS type + * @return string Normalized DBMS type + */ + public function normalizeType($type) { + return $type === 'sqlite' ? 'sqlite3' : $type; + } + + /** + * Checks whether the specified DBMS type is valid. + * + * @param string $type + * @return bool + */ + public function isValidType($type) { + $normalizedType = $this->normalizeType($type); + return isset($this->defaultConnectionParams[$normalizedType]); + } + + /** + * Create the connection parameters for the config + * + * @return array + */ + public function createConnectionParams() { + $type = $this->config->getValue('dbtype', 'sqlite'); + + $connectionParams = [ + 'user' => $this->config->getValue('dbuser', ''), + 'password' => $this->config->getValue('dbpassword', ''), + ]; + $name = $this->config->getValue('dbname', self::DEFAULT_DBNAME); + + if ($this->normalizeType($type) === 'sqlite3') { + $dataDir = $this->config->getValue("datadirectory", \OC::$SERVERROOT . '/data'); + $connectionParams['path'] = $dataDir . '/' . $name . '.db'; + } else { + $host = $this->config->getValue('dbhost', ''); + $connectionParams = array_merge($connectionParams, $this->splitHostFromPortAndSocket($host)); + $connectionParams['dbname'] = $name; + } + + $connectionParams['tablePrefix'] = $this->config->getValue('dbtableprefix', self::DEFAULT_DBTABLEPREFIX); + $connectionParams['sqlite.journal_mode'] = $this->config->getValue('sqlite.journal_mode', 'WAL'); + + //additional driver options, eg. for mysql ssl + $driverOptions = $this->config->getValue('dbdriveroptions', null); + if ($driverOptions) { + $connectionParams['driverOptions'] = $driverOptions; + } + + // set default table creation options + $connectionParams['defaultTableOptions'] = [ + 'collate' => 'utf8_bin', + 'tablePrefix' => $connectionParams['tablePrefix'] + ]; + + if ($this->config->getValue('mysql.utf8mb4', false)) { + $connectionParams['defaultTableOptions'] = [ + 'collate' => 'utf8mb4_bin', + 'charset' => 'utf8mb4', + 'row_format' => 'compressed', + 'tablePrefix' => $connectionParams['tablePrefix'] + ]; + } + + return $connectionParams; + } + + /** + * @param string $host + * @return array + */ + protected function splitHostFromPortAndSocket($host): array { + $params = [ + 'host' => $host, + ]; + + $matches = []; + if (preg_match('/^(.*):([^\]:]+)$/', $host, $matches)) { + // Host variable carries a port or socket. + $params['host'] = $matches[1]; + if (is_numeric($matches[2])) { + $params['port'] = (int) $matches[2]; + } else { + $params['unix_socket'] = $matches[2]; + } + } + + return $params; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaManager.php b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaManager.php new file mode 100644 index 0000000..7808e0c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaManager.php @@ -0,0 +1,173 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Schema\Schema; +use OCP\IDBConnection; + +class MDB2SchemaManager { + /** @var \OC\DB\Connection $conn */ + protected $conn; + + /** + * @param IDBConnection $conn + */ + public function __construct($conn) { + $this->conn = $conn; + } + + /** + * saves database scheme to xml file + * @param string $file name of file + * @return bool + * + * TODO: write more documentation + */ + public function getDbStructure($file) { + return \OC\DB\MDB2SchemaWriter::saveSchemaToFile($file, $this->conn); + } + + /** + * Creates tables from XML file + * @param string $file file to read structure from + * @return bool + * + * TODO: write more documentation + */ + public function createDbFromStructure($file) { + $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $this->conn->getDatabasePlatform()); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + $toSchema = $schemaReader->loadSchemaFromFile($file, $toSchema); + return $this->executeSchemaChange($toSchema); + } + + /** + * @return \OC\DB\Migrator + */ + public function getMigrator() { + $random = \OC::$server->getSecureRandom(); + $platform = $this->conn->getDatabasePlatform(); + $config = \OC::$server->getConfig(); + $dispatcher = \OC::$server->getEventDispatcher(); + if ($platform instanceof SqlitePlatform) { + return new SQLiteMigrator($this->conn, $random, $config, $dispatcher); + } elseif ($platform instanceof OraclePlatform) { + return new OracleMigrator($this->conn, $random, $config, $dispatcher); + } elseif ($platform instanceof MySqlPlatform) { + return new MySQLMigrator($this->conn, $random, $config, $dispatcher); + } elseif ($platform instanceof PostgreSqlPlatform) { + return new PostgreSqlMigrator($this->conn, $random, $config, $dispatcher); + } else { + return new Migrator($this->conn, $random, $config, $dispatcher); + } + } + + /** + * Reads database schema from file + * + * @param string $file file to read from + * @return \Doctrine\DBAL\Schema\Schema + */ + private function readSchemaFromFile($file) { + $platform = $this->conn->getDatabasePlatform(); + $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $platform); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + return $schemaReader->loadSchemaFromFile($file, $toSchema); + } + + /** + * update the database scheme + * @param string $file file to read structure from + * @param bool $generateSql only return the sql needed for the upgrade + * @return string|boolean + */ + public function updateDbFromStructure($file, $generateSql = false) { + $toSchema = $this->readSchemaFromFile($file); + $migrator = $this->getMigrator(); + + if ($generateSql) { + return $migrator->generateChangeScript($toSchema); + } else { + $migrator->migrate($toSchema); + return true; + } + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @return string + */ + public function generateChangeScript($schema) { + $migrator = $this->getMigrator(); + return $migrator->generateChangeScript($schema); + } + + /** + * remove all tables defined in a database structure xml file + * + * @param string $file the xml file describing the tables + */ + public function removeDBStructure($file) { + $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $this->conn->getDatabasePlatform()); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + $fromSchema = $schemaReader->loadSchemaFromFile($file, $toSchema); + $toSchema = clone $fromSchema; + foreach ($toSchema->getTables() as $table) { + $toSchema->dropTable($table->getName()); + } + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + $this->executeSchemaChange($schemaDiff); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema|\Doctrine\DBAL\Schema\SchemaDiff $schema + * @return bool + */ + private function executeSchemaChange($schema) { + $this->conn->beginTransaction(); + foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->query($sql); + } + $this->conn->commit(); + + if ($this->conn->getDatabasePlatform() instanceof SqlitePlatform) { + $this->conn->close(); + $this->conn->connect(); + } + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaReader.php b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaReader.php new file mode 100644 index 0000000..b371e1a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaReader.php @@ -0,0 +1,347 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Oliver Gasser + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Schema; +use OCP\IConfig; + +class MDB2SchemaReader { + + /** + * @var string $DBTABLEPREFIX + */ + protected $DBTABLEPREFIX; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + protected $platform; + + /** @var IConfig */ + protected $config; + + /** + * @param \OCP\IConfig $config + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(IConfig $config, AbstractPlatform $platform) { + $this->platform = $platform; + $this->config = $config; + $this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_'); + } + + /** + * @param string $file + * @param Schema $schema + * @return Schema + * @throws \DomainException + */ + public function loadSchemaFromFile($file, Schema $schema) { + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($file); + libxml_disable_entity_loader($loadEntities); + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + case 'create': + case 'overwrite': + case 'charset': + break; + case 'table': + $this->loadTable($schema, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + return $schema; + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadTable($schema, $xml) { + $table = null; + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + $name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name); + $name = $this->platform->quoteIdentifier($name); + $table = $schema->createTable($name); + break; + case 'create': + case 'overwrite': + case 'charset': + break; + case 'declaration': + if (is_null($table)) { + throw new \DomainException('Table declaration before table name'); + } + $this->loadDeclaration($table, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadDeclaration($table, $xml) { + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'field': + $this->loadField($table, $child); + break; + case 'index': + $this->loadIndex($table, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadField($table, $xml) { + $options = [ 'notnull' => false ]; + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + $name = $this->platform->quoteIdentifier($name); + break; + case 'type': + $type = (string)$child; + switch ($type) { + case 'text': + $type = 'string'; + break; + case 'clob': + $type = 'text'; + break; + case 'timestamp': + $type = 'datetime'; + break; + case 'numeric': + $type = 'decimal'; + break; + } + break; + case 'length': + $length = (string)$child; + $options['length'] = $length; + break; + case 'unsigned': + $unsigned = $this->asBool($child); + $options['unsigned'] = $unsigned; + break; + case 'notnull': + $notnull = $this->asBool($child); + $options['notnull'] = $notnull; + break; + case 'autoincrement': + $autoincrement = $this->asBool($child); + $options['autoincrement'] = $autoincrement; + break; + case 'default': + $default = (string)$child; + $options['default'] = $default; + break; + case 'comments': + $comment = (string)$child; + $options['comment'] = $comment; + break; + case 'primary': + $primary = $this->asBool($child); + $options['primary'] = $primary; + break; + case 'precision': + $precision = (string)$child; + $options['precision'] = $precision; + break; + case 'scale': + $scale = (string)$child; + $options['scale'] = $scale; + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + if (isset($name) && isset($type)) { + if (isset($options['default']) && empty($options['default'])) { + if (empty($options['notnull']) || !$options['notnull']) { + unset($options['default']); + $options['notnull'] = false; + } else { + $options['default'] = ''; + } + if ($type == 'integer' || $type == 'decimal') { + $options['default'] = 0; + } elseif ($type == 'boolean') { + $options['default'] = false; + } + if (!empty($options['autoincrement']) && $options['autoincrement']) { + unset($options['default']); + } + } + if ($type === 'integer' && isset($options['default'])) { + $options['default'] = (int)$options['default']; + } + if ($type === 'integer' && isset($options['length'])) { + $length = $options['length']; + if ($length < 4) { + $type = 'smallint'; + } elseif ($length > 4) { + $type = 'bigint'; + } + } + if ($type === 'boolean' && isset($options['default'])) { + $options['default'] = $this->asBool($options['default']); + } + if (!empty($options['autoincrement']) + && !empty($options['notnull']) + ) { + $options['primary'] = true; + } + + $table->addColumn($name, $type, $options); + if (!empty($options['primary']) && $options['primary']) { + $table->setPrimaryKey([$name]); + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadIndex($table, $xml) { + $name = null; + $fields = []; + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + break; + case 'primary': + $primary = $this->asBool($child); + break; + case 'unique': + $unique = $this->asBool($child); + break; + case 'field': + foreach ($child->children() as $field) { + /** + * @var \SimpleXMLElement $field + */ + switch ($field->getName()) { + case 'name': + $field_name = (string)$field; + $field_name = $this->platform->quoteIdentifier($field_name); + $fields[] = $field_name; + break; + case 'sorting': + break; + default: + throw new \DomainException('Unknown element: ' . $field->getName()); + + } + } + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + if (!empty($fields)) { + if (isset($primary) && $primary) { + if ($table->hasPrimaryKey()) { + return; + } + $table->setPrimaryKey($fields, $name); + } else { + if (isset($unique) && $unique) { + $table->addUniqueIndex($fields, $name); + } else { + $table->addIndex($fields, $name); + } + } + } else { + throw new \DomainException('Empty index definition: ' . $name . ' options:' . print_r($fields, true)); + } + } + + /** + * @param \SimpleXMLElement|string $xml + * @return bool + */ + private function asBool($xml) { + $result = (string)$xml; + if ($result == 'true') { + $result = true; + } elseif ($result == 'false') { + $result = false; + } + return (bool)$result; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaWriter.php b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaWriter.php new file mode 100644 index 0000000..2c1f6a5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MDB2SchemaWriter.php @@ -0,0 +1,181 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author tbelau666 + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Index; + +class MDB2SchemaWriter { + + /** + * @param string $file + * @param \OC\DB\Connection $conn + * @return bool + */ + public static function saveSchemaToFile($file, \OC\DB\Connection $conn) { + $config = \OC::$server->getConfig(); + + $xml = new \SimpleXMLElement(''); + $xml->addChild('name', $config->getSystemValue('dbname', 'owncloud')); + $xml->addChild('create', 'true'); + $xml->addChild('overwrite', 'false'); + if ($config->getSystemValue('dbtype', 'sqlite') === 'mysql' && $config->getSystemValue('mysql.utf8mb4', false)) { + $xml->addChild('charset', 'utf8mb4'); + } else { + $xml->addChild('charset', 'utf8'); + } + + // FIX ME: bloody work around + if ($config->getSystemValue('dbtype', 'sqlite') === 'oci') { + $filterExpression = '/^"' . preg_quote($conn->getPrefix()) . '/'; + } else { + $filterExpression = '/^' . preg_quote($conn->getPrefix()) . '/'; + } + $conn->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression); + + foreach ($conn->getSchemaManager()->listTables() as $table) { + self::saveTable($table, $xml->addChild('table')); + } + file_put_contents($file, $xml->asXML()); + return true; + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + */ + private static function saveTable($table, $xml) { + $xml->addChild('name', $table->getName()); + $declaration = $xml->addChild('declaration'); + foreach ($table->getColumns() as $column) { + self::saveColumn($column, $declaration->addChild('field')); + } + foreach ($table->getIndexes() as $index) { + if ($index->getName() == 'PRIMARY') { + $autoincrement = false; + foreach ($index->getColumns() as $column) { + if ($table->getColumn($column)->getAutoincrement()) { + $autoincrement = true; + } + } + if ($autoincrement) { + continue; + } + } + self::saveIndex($index, $declaration->addChild('index')); + } + } + + /** + * @param Column $column + * @param \SimpleXMLElement $xml + */ + private static function saveColumn($column, $xml) { + $xml->addChild('name', $column->getName()); + switch ($column->getType()) { + case 'SmallInt': + case 'Integer': + case 'BigInt': + $xml->addChild('type', 'integer'); + $default = $column->getDefault(); + if (is_null($default) && $column->getAutoincrement()) { + $default = '0'; + } + $xml->addChild('default', $default); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + if ($column->getAutoincrement()) { + $xml->addChild('autoincrement', '1'); + } + if ($column->getUnsigned()) { + $xml->addChild('unsigned', 'true'); + } + $length = '4'; + if ($column->getType() == 'SmallInt') { + $length = '2'; + } elseif ($column->getType() == 'BigInt') { + $length = '8'; + } + $xml->addChild('length', $length); + break; + case 'String': + $xml->addChild('type', 'text'); + $default = trim($column->getDefault()); + if ($default === '') { + $default = false; + } + $xml->addChild('default', $default); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', $column->getLength()); + break; + case 'Text': + $xml->addChild('type', 'clob'); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + break; + case 'Decimal': + $xml->addChild('type', 'decimal'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', '15'); + break; + case 'Boolean': + $xml->addChild('type', 'integer'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', '1'); + break; + case 'DateTime': + $xml->addChild('type', 'timestamp'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + break; + + } + } + + /** + * @param Index $index + * @param \SimpleXMLElement $xml + */ + private static function saveIndex($index, $xml) { + $xml->addChild('name', $index->getName()); + if ($index->isPrimary()) { + $xml->addChild('primary', 'true'); + } elseif ($index->isUnique()) { + $xml->addChild('unique', 'true'); + } + foreach ($index->getColumns() as $column) { + $field = $xml->addChild('field'); + $field->addChild('name', $column); + $field->addChild('sorting', 'ascending'); + } + } + + private static function toBool($bool) { + return $bool ? 'true' : 'false'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MigrationException.php b/docker/overlays/nextcloud/html/lib/private/DB/MigrationException.php new file mode 100644 index 0000000..76d769e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MigrationException.php @@ -0,0 +1,40 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class MigrationException extends \Exception { + private $table; + + public function __construct($table, $message) { + $this->table = $table; + parent::__construct($message); + } + + /** + * @return string + */ + public function getTable() { + return $this->table; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MigrationService.php b/docker/overlays/nextcloud/html/lib/private/DB/MigrationService.php new file mode 100644 index 0000000..cd02801 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MigrationService.php @@ -0,0 +1,576 @@ + + * @copyright Copyright (c) 2017, ownCloud GmbH + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Types; +use OC\App\InfoParser; +use OC\IntegrityCheck\Helpers\AppLocator; +use OC\Migration\SimpleOutput; +use OCP\AppFramework\App; +use OCP\AppFramework\QueryException; +use OCP\IDBConnection; +use OCP\Migration\IMigrationStep; +use OCP\Migration\IOutput; + +class MigrationService { + + /** @var boolean */ + private $migrationTableCreated; + /** @var array */ + private $migrations; + /** @var IOutput */ + private $output; + /** @var Connection */ + private $connection; + /** @var string */ + private $appName; + /** @var bool */ + private $checkOracle; + + /** + * MigrationService constructor. + * + * @param $appName + * @param IDBConnection $connection + * @param AppLocator $appLocator + * @param IOutput|null $output + * @throws \Exception + */ + public function __construct($appName, IDBConnection $connection, IOutput $output = null, AppLocator $appLocator = null) { + $this->appName = $appName; + $this->connection = $connection; + $this->output = $output; + if (null === $this->output) { + $this->output = new SimpleOutput(\OC::$server->getLogger(), $appName); + } + + if ($appName === 'core') { + $this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations'; + $this->migrationsNamespace = 'OC\\Core\\Migrations'; + $this->checkOracle = true; + } else { + if (null === $appLocator) { + $appLocator = new AppLocator(); + } + $appPath = $appLocator->getAppPath($appName); + $namespace = App::buildAppNamespace($appName); + $this->migrationsPath = "$appPath/lib/Migration"; + $this->migrationsNamespace = $namespace . '\\Migration'; + + $infoParser = new InfoParser(); + $info = $infoParser->parse($appPath . '/appinfo/info.xml'); + if (!isset($info['dependencies']['database'])) { + $this->checkOracle = true; + } else { + $this->checkOracle = false; + foreach ($info['dependencies']['database'] as $database) { + if (\is_string($database) && $database === 'oci') { + $this->checkOracle = true; + } elseif (\is_array($database) && isset($database['@value']) && $database['@value'] === 'oci') { + $this->checkOracle = true; + } + } + } + } + } + + /** + * Returns the name of the app for which this migration is executed + * + * @return string + */ + public function getApp() { + return $this->appName; + } + + /** + * @return bool + * @codeCoverageIgnore - this will implicitly tested on installation + */ + private function createMigrationTable() { + if ($this->migrationTableCreated) { + return false; + } + + $schema = new SchemaWrapper($this->connection); + + /** + * We drop the table when it has different columns or the definition does not + * match. E.g. ownCloud uses a length of 177 for app and 14 for version. + */ + try { + $table = $schema->getTable('migrations'); + $columns = $table->getColumns(); + + if (count($columns) === 2) { + try { + $column = $table->getColumn('app'); + $schemaMismatch = $column->getLength() !== 255; + + if (!$schemaMismatch) { + $column = $table->getColumn('version'); + $schemaMismatch = $column->getLength() !== 255; + } + } catch (SchemaException $e) { + // One of the columns is missing + $schemaMismatch = true; + } + + if (!$schemaMismatch) { + // Table exists and schema matches: return back! + $this->migrationTableCreated = true; + return false; + } + } + + // Drop the table, when it didn't match our expectations. + $this->connection->dropTable('migrations'); + + // Recreate the schema after the table was dropped. + $schema = new SchemaWrapper($this->connection); + } catch (SchemaException $e) { + // Table not found, no need to panic, we will create it. + } + + $table = $schema->createTable('migrations'); + $table->addColumn('app', Types::STRING, ['length' => 255]); + $table->addColumn('version', Types::STRING, ['length' => 255]); + $table->setPrimaryKey(['app', 'version']); + + $this->connection->migrateToSchema($schema->getWrappedSchema()); + + $this->migrationTableCreated = true; + + return true; + } + + /** + * Returns all versions which have already been applied + * + * @return string[] + * @codeCoverageIgnore - no need to test this + */ + public function getMigratedVersions() { + $this->createMigrationTable(); + $qb = $this->connection->getQueryBuilder(); + + $qb->select('version') + ->from('migrations') + ->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp()))) + ->orderBy('version'); + + $result = $qb->execute(); + $rows = $result->fetchAll(\PDO::FETCH_COLUMN); + $result->closeCursor(); + + return $rows; + } + + /** + * Returns all versions which are available in the migration folder + * + * @return array + */ + public function getAvailableVersions() { + $this->ensureMigrationsAreLoaded(); + return array_map('strval', array_keys($this->migrations)); + } + + protected function findMigrations() { + $directory = realpath($this->migrationsPath); + if ($directory === false || !file_exists($directory) || !is_dir($directory)) { + return []; + } + + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '#^.+\\/Version[^\\/]{1,255}\\.php$#i', + \RegexIterator::GET_MATCH); + + $files = array_keys(iterator_to_array($iterator)); + uasort($files, function ($a, $b) { + preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA); + preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB); + if (!empty($matchA) && !empty($matchB)) { + if ($matchA[1] !== $matchB[1]) { + return ($matchA[1] < $matchB[1]) ? -1 : 1; + } + return ($matchA[2] < $matchB[2]) ? -1 : 1; + } + return (basename($a) < basename($b)) ? -1 : 1; + }); + + $migrations = []; + + foreach ($files as $file) { + $className = basename($file, '.php'); + $version = (string) substr($className, 7); + if ($version === '0') { + throw new \InvalidArgumentException( + "Cannot load a migrations with the name '$version' because it is a reserved number" + ); + } + $migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className); + } + + return $migrations; + } + + /** + * @param string $to + * @return string[] + */ + private function getMigrationsToExecute($to) { + $knownMigrations = $this->getMigratedVersions(); + $availableMigrations = $this->getAvailableVersions(); + + $toBeExecuted = []; + foreach ($availableMigrations as $v) { + if ($to !== 'latest' && $v > $to) { + continue; + } + if ($this->shallBeExecuted($v, $knownMigrations)) { + $toBeExecuted[] = $v; + } + } + + return $toBeExecuted; + } + + /** + * @param string $m + * @param string[] $knownMigrations + * @return bool + */ + private function shallBeExecuted($m, $knownMigrations) { + if (in_array($m, $knownMigrations)) { + return false; + } + + return true; + } + + /** + * @param string $version + */ + private function markAsExecuted($version) { + $this->connection->insertIfNotExist('*PREFIX*migrations', [ + 'app' => $this->appName, + 'version' => $version + ]); + } + + /** + * Returns the name of the table which holds the already applied versions + * + * @return string + */ + public function getMigrationsTableName() { + return $this->connection->getPrefix() . 'migrations'; + } + + /** + * Returns the namespace of the version classes + * + * @return string + */ + public function getMigrationsNamespace() { + return $this->migrationsNamespace; + } + + /** + * Returns the directory which holds the versions + * + * @return string + */ + public function getMigrationsDirectory() { + return $this->migrationsPath; + } + + /** + * Return the explicit version for the aliases; current, next, prev, latest + * + * @param string $alias + * @return mixed|null|string + */ + public function getMigration($alias) { + switch ($alias) { + case 'current': + return $this->getCurrentVersion(); + case 'next': + return $this->getRelativeVersion($this->getCurrentVersion(), 1); + case 'prev': + return $this->getRelativeVersion($this->getCurrentVersion(), -1); + case 'latest': + $this->ensureMigrationsAreLoaded(); + + $migrations = $this->getAvailableVersions(); + return @end($migrations); + } + return '0'; + } + + /** + * @param string $version + * @param int $delta + * @return null|string + */ + private function getRelativeVersion($version, $delta) { + $this->ensureMigrationsAreLoaded(); + + $versions = $this->getAvailableVersions(); + array_unshift($versions, 0); + $offset = array_search($version, $versions, true); + if ($offset === false || !isset($versions[$offset + $delta])) { + // Unknown version or delta out of bounds. + return null; + } + + return (string) $versions[$offset + $delta]; + } + + /** + * @return string + */ + private function getCurrentVersion() { + $m = $this->getMigratedVersions(); + if (count($m) === 0) { + return '0'; + } + $migrations = array_values($m); + return @end($migrations); + } + + /** + * @param string $version + * @return string + * @throws \InvalidArgumentException + */ + private function getClass($version) { + $this->ensureMigrationsAreLoaded(); + + if (isset($this->migrations[$version])) { + return $this->migrations[$version]; + } + + throw new \InvalidArgumentException("Version $version is unknown."); + } + + /** + * Allows to set an IOutput implementation which is used for logging progress and messages + * + * @param IOutput $output + */ + public function setOutput(IOutput $output) { + $this->output = $output; + } + + /** + * Applies all not yet applied versions up to $to + * + * @param string $to + * @param bool $schemaOnly + * @throws \InvalidArgumentException + */ + public function migrate($to = 'latest', $schemaOnly = false) { + // read known migrations + $toBeExecuted = $this->getMigrationsToExecute($to); + foreach ($toBeExecuted as $version) { + $this->executeStep($version, $schemaOnly); + } + } + + /** + * Get the human readable descriptions for the migration steps to run + * + * @param string $to + * @return string[] [$name => $description] + */ + public function describeMigrationStep($to = 'latest') { + $toBeExecuted = $this->getMigrationsToExecute($to); + $description = []; + foreach ($toBeExecuted as $version) { + $migration = $this->createInstance($version); + if ($migration->name()) { + $description[$migration->name()] = $migration->description(); + } + } + return $description; + } + + /** + * @param string $version + * @return IMigrationStep + * @throws \InvalidArgumentException + */ + protected function createInstance($version) { + $class = $this->getClass($version); + try { + $s = \OC::$server->query($class); + + if (!$s instanceof IMigrationStep) { + throw new \InvalidArgumentException('Not a valid migration'); + } + } catch (QueryException $e) { + if (class_exists($class)) { + $s = new $class(); + } else { + throw new \InvalidArgumentException("Migration step '$class' is unknown"); + } + } + + return $s; + } + + /** + * Executes one explicit version + * + * @param string $version + * @param bool $schemaOnly + * @throws \InvalidArgumentException + */ + public function executeStep($version, $schemaOnly = false) { + $instance = $this->createInstance($version); + + if (!$schemaOnly) { + $instance->preSchemaChange($this->output, function () { + return new SchemaWrapper($this->connection); + }, ['tablePrefix' => $this->connection->getPrefix()]); + } + + $toSchema = $instance->changeSchema($this->output, function () { + return new SchemaWrapper($this->connection); + }, ['tablePrefix' => $this->connection->getPrefix()]); + + if ($toSchema instanceof SchemaWrapper) { + $targetSchema = $toSchema->getWrappedSchema(); + if ($this->checkOracle) { + $sourceSchema = $this->connection->createSchema(); + $this->ensureOracleIdentifierLengthLimit($sourceSchema, $targetSchema, strlen($this->connection->getPrefix())); + } + $this->connection->migrateToSchema($targetSchema); + $toSchema->performDropTableCalls(); + } + + if (!$schemaOnly) { + $instance->postSchemaChange($this->output, function () { + return new SchemaWrapper($this->connection); + }, ['tablePrefix' => $this->connection->getPrefix()]); + } + + $this->markAsExecuted($version); + } + + public function ensureOracleIdentifierLengthLimit(Schema $sourceSchema, Schema $targetSchema, int $prefixLength) { + $sequences = $targetSchema->getSequences(); + + foreach ($targetSchema->getTables() as $table) { + try { + $sourceTable = $sourceSchema->getTable($table->getName()); + } catch (SchemaException $e) { + if (\strlen($table->getName()) - $prefixLength > 27) { + throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.'); + } + $sourceTable = null; + } + + foreach ($table->getColumns() as $thing) { + if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) { + throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); + } + + if ($thing->getNotnull() && $thing->getDefault() === '' + && $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) { + throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.'); + } + } + + foreach ($table->getIndexes() as $thing) { + if ((!$sourceTable instanceof Table || !$sourceTable->hasIndex($thing->getName())) && \strlen($thing->getName()) > 30) { + throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); + } + } + + foreach ($table->getForeignKeys() as $thing) { + if ((!$sourceTable instanceof Table || !$sourceTable->hasForeignKey($thing->getName())) && \strlen($thing->getName()) > 30) { + throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); + } + } + + $primaryKey = $table->getPrimaryKey(); + if ($primaryKey instanceof Index && (!$sourceTable instanceof Table || !$sourceTable->hasPrimaryKey())) { + $indexName = strtolower($primaryKey->getName()); + $isUsingDefaultName = $indexName === 'primary'; + + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + $defaultName = $table->getName() . '_pkey'; + $isUsingDefaultName = strtolower($defaultName) === $indexName; + + if ($isUsingDefaultName) { + $sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq'; + $sequences = array_filter($sequences, function (Sequence $sequence) use ($sequenceName) { + return $sequence->getName() !== $sequenceName; + }); + } + } elseif ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + $defaultName = $table->getName() . '_seq'; + $isUsingDefaultName = strtolower($defaultName) === $indexName; + } + + if (!$isUsingDefaultName && \strlen($indexName) > 30) { + throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.'); + } + if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) { + throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.'); + } + } + } + + foreach ($sequences as $sequence) { + if (!$sourceSchema->hasSequence($sequence->getName()) && \strlen($sequence->getName()) > 30) { + throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.'); + } + } + } + + private function ensureMigrationsAreLoaded() { + if (empty($this->migrations)) { + $this->migrations = $this->findMigrations(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/Migrator.php b/docker/overlays/nextcloud/html/lib/private/DB/Migrator.php new file mode 100644 index 0000000..be3d655 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/Migrator.php @@ -0,0 +1,316 @@ + + * @author Joas Schilling + * @author martin-rueegg + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author tbelau666 + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Comparator; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\SchemaConfig; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\StringType; +use Doctrine\DBAL\Types\Type; +use OCP\IConfig; +use OCP\Security\ISecureRandom; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Migrator { + + /** @var \Doctrine\DBAL\Connection */ + protected $connection; + + /** @var ISecureRandom */ + private $random; + + /** @var IConfig */ + protected $config; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + /** @var bool */ + private $noEmit = false; + + /** + * @param \Doctrine\DBAL\Connection $connection + * @param ISecureRandom $random + * @param IConfig $config + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(\Doctrine\DBAL\Connection $connection, + ISecureRandom $random, + IConfig $config, + EventDispatcherInterface $dispatcher = null) { + $this->connection = $connection; + $this->random = $random; + $this->config = $config; + $this->dispatcher = $dispatcher; + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $targetSchema + */ + public function migrate(Schema $targetSchema) { + $this->noEmit = true; + $this->applySchema($targetSchema); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $targetSchema + * @return string + */ + public function generateChangeScript(Schema $targetSchema) { + $schemaDiff = $this->getDiff($targetSchema, $this->connection); + + $script = ''; + $sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform()); + foreach ($sqls as $sql) { + $script .= $this->convertStatementToScript($sql); + } + + return $script; + } + + /** + * @param Schema $targetSchema + * @throws \OC\DB\MigrationException + */ + public function checkMigrate(Schema $targetSchema) { + $this->noEmit = true; + /**@var \Doctrine\DBAL\Schema\Table[] $tables */ + $tables = $targetSchema->getTables(); + $filterExpression = $this->getFilterExpression(); + $this->connection->getConfiguration()-> + setFilterSchemaAssetsExpression($filterExpression); + $existingTables = $this->connection->getSchemaManager()->listTableNames(); + + $step = 0; + foreach ($tables as $table) { + if (strpos($table->getName(), '.')) { + list(, $tableName) = explode('.', $table->getName()); + } else { + $tableName = $table->getName(); + } + $this->emitCheckStep($tableName, $step++, count($tables)); + // don't need to check for new tables + if (array_search($tableName, $existingTables) !== false) { + $this->checkTableMigrate($table); + } + } + } + + /** + * Create a unique name for the temporary table + * + * @param string $name + * @return string + */ + protected function generateTemporaryTableName($name) { + return $this->config->getSystemValue('dbtableprefix', 'oc_') . $name . '_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + } + + /** + * Check the migration of a table on a copy so we can detect errors before messing with the real table + * + * @param \Doctrine\DBAL\Schema\Table $table + * @throws \OC\DB\MigrationException + */ + protected function checkTableMigrate(Table $table) { + $name = $table->getName(); + $tmpName = $this->generateTemporaryTableName($name); + + $this->copyTable($name, $tmpName); + + //create the migration schema for the temporary table + $tmpTable = $this->renameTableSchema($table, $tmpName); + $schemaConfig = new SchemaConfig(); + $schemaConfig->setName($this->connection->getDatabase()); + $schema = new Schema([$tmpTable], [], $schemaConfig); + + try { + $this->applySchema($schema); + $this->dropTable($tmpName); + } catch (DBALException $e) { + // pgsql needs to commit it's failed transaction before doing anything else + if ($this->connection->isTransactionActive()) { + $this->connection->commit(); + } + $this->dropTable($tmpName); + throw new MigrationException($table->getName(), $e->getMessage()); + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param string $newName + * @return \Doctrine\DBAL\Schema\Table + */ + protected function renameTableSchema(Table $table, $newName) { + /** + * @var \Doctrine\DBAL\Schema\Index[] $indexes + */ + $indexes = $table->getIndexes(); + $newIndexes = []; + foreach ($indexes as $index) { + if ($index->isPrimary()) { + // do not rename primary key + $indexName = $index->getName(); + } else { + // avoid conflicts in index names + $indexName = $this->config->getSystemValue('dbtableprefix', 'oc_') . $this->random->generate(13, ISecureRandom::CHAR_LOWER); + } + $newIndexes[] = new Index($indexName, $index->getColumns(), $index->isUnique(), $index->isPrimary()); + } + + // foreign keys are not supported so we just set it to an empty array + return new Table($newName, $table->getColumns(), $newIndexes, [], 0, $table->getOptions()); + } + + public function createSchema() { + $filterExpression = $this->getFilterExpression(); + $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression); + return $this->connection->getSchemaManager()->createSchema(); + } + + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + * @throws DBALException + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + // adjust varchar columns with a length higher then getVarcharMaxLength to clob + foreach ($targetSchema->getTables() as $table) { + foreach ($table->getColumns() as $column) { + if ($column->getType() instanceof StringType) { + if ($column->getLength() > $connection->getDatabasePlatform()->getVarcharMaxLength()) { + $column->setType(Type::getType('text')); + $column->setLength(null); + } + } + } + } + + $filterExpression = $this->getFilterExpression(); + $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression); + $sourceSchema = $connection->getSchemaManager()->createSchema(); + + // remove tables we don't know about + foreach ($sourceSchema->getTables() as $table) { + if (!$targetSchema->hasTable($table->getName())) { + $sourceSchema->dropTable($table->getName()); + } + } + // remove sequences we don't know about + foreach ($sourceSchema->getSequences() as $table) { + if (!$targetSchema->hasSequence($table->getName())) { + $sourceSchema->dropSequence($table->getName()); + } + } + + $comparator = new Comparator(); + return $comparator->compare($sourceSchema, $targetSchema); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + */ + protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) { + if (is_null($connection)) { + $connection = $this->connection; + } + + $schemaDiff = $this->getDiff($targetSchema, $connection); + + $connection->beginTransaction(); + $sqls = $schemaDiff->toSql($connection->getDatabasePlatform()); + $step = 0; + foreach ($sqls as $sql) { + $this->emit($sql, $step++, count($sqls)); + $connection->query($sql); + } + $connection->commit(); + } + + /** + * @param string $sourceName + * @param string $targetName + */ + protected function copyTable($sourceName, $targetName) { + $quotedSource = $this->connection->quoteIdentifier($sourceName); + $quotedTarget = $this->connection->quoteIdentifier($targetName); + + $this->connection->exec('CREATE TABLE ' . $quotedTarget . ' (LIKE ' . $quotedSource . ')'); + $this->connection->exec('INSERT INTO ' . $quotedTarget . ' SELECT * FROM ' . $quotedSource); + } + + /** + * @param string $name + */ + protected function dropTable($name) { + $this->connection->exec('DROP TABLE ' . $this->connection->quoteIdentifier($name)); + } + + /** + * @param $statement + * @return string + */ + protected function convertStatementToScript($statement) { + $script = $statement . ';'; + $script .= PHP_EOL; + $script .= PHP_EOL; + return $script; + } + + protected function getFilterExpression() { + return '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/'; + } + + protected function emit($sql, $step, $max) { + if ($this->noEmit) { + return; + } + if (is_null($this->dispatcher)) { + return; + } + $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max])); + } + + private function emitCheckStep($tableName, $step, $max) { + if (is_null($this->dispatcher)) { + return; + } + $this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step+1, $max])); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MissingColumnInformation.php b/docker/overlays/nextcloud/html/lib/private/DB/MissingColumnInformation.php new file mode 100644 index 0000000..d1c81c2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MissingColumnInformation.php @@ -0,0 +1,42 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB; + +class MissingColumnInformation { + private $listOfMissingColumns = []; + + public function addHintForMissingColumn(string $tableName, string $columnName): void { + $this->listOfMissingColumns[] = [ + 'tableName' => $tableName, + 'columnName' => $columnName, + ]; + } + + public function getListOfMissingColumns(): array { + return $this->listOfMissingColumns; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MissingIndexInformation.php b/docker/overlays/nextcloud/html/lib/private/DB/MissingIndexInformation.php new file mode 100644 index 0000000..04853dc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MissingIndexInformation.php @@ -0,0 +1,43 @@ + + * + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB; + +class MissingIndexInformation { + private $listOfMissingIndexes = []; + + public function addHintForMissingSubject(string $tableName, string $indexName) { + $this->listOfMissingIndexes[] = [ + 'tableName' => $tableName, + 'indexName' => $indexName + ]; + } + + public function getListOfMissingIndexes(): array { + return $this->listOfMissingIndexes; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MySQLMigrator.php b/docker/overlays/nextcloud/html/lib/private/DB/MySQLMigrator.php new file mode 100644 index 0000000..58c5468 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MySQLMigrator.php @@ -0,0 +1,76 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; + +class MySQLMigrator extends Migrator { + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $platform = $connection->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('enum', 'string'); + $platform->registerDoctrineTypeMapping('bit', 'string'); + + $schemaDiff = parent::getDiff($targetSchema, $connection); + + // identifiers need to be quoted for mysql + foreach ($schemaDiff->changedTables as $tableDiff) { + $tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name); + foreach ($tableDiff->changedColumns as $column) { + $column->oldColumnName = $this->connection->quoteIdentifier($column->oldColumnName); + } + } + + return $schemaDiff; + } + + /** + * Speed up migration test by disabling autocommit and unique indexes check + * + * @param \Doctrine\DBAL\Schema\Table $table + * @throws \OC\DB\MigrationException + */ + protected function checkTableMigrate(Table $table) { + $this->connection->exec('SET autocommit=0'); + $this->connection->exec('SET unique_checks=0'); + + try { + parent::checkTableMigrate($table); + } catch (\Exception $e) { + $this->connection->exec('SET unique_checks=1'); + $this->connection->exec('SET autocommit=1'); + throw new MigrationException($table->getName(), $e->getMessage()); + } + $this->connection->exec('SET unique_checks=1'); + $this->connection->exec('SET autocommit=1'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/MySqlTools.php b/docker/overlays/nextcloud/html/lib/private/DB/MySqlTools.php new file mode 100644 index 0000000..007388d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/MySqlTools.php @@ -0,0 +1,71 @@ + + * @author Joas Schilling + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use OCP\IDBConnection; + +/** + * Various MySQL specific helper functions. + */ +class MySqlTools { + + /** + * @param Connection $connection + * @return bool + */ + public function supports4ByteCharset(IDBConnection $connection) { + $variables = ['innodb_file_per_table' => 'ON']; + if (!$this->isMariaDBWithLargePrefix($connection)) { + $variables['innodb_file_format'] = 'Barracuda'; + $variables['innodb_large_prefix'] = 'ON'; + } + + foreach ($variables as $var => $val) { + $result = $connection->executeQuery("SHOW VARIABLES LIKE '$var'"); + $row = $result->fetch(); + $result->closeCursor(); + if ($row === false) { + return false; + } + if (strcasecmp($row['Value'], $val) !== 0) { + return false; + } + } + return true; + } + + protected function isMariaDBWithLargePrefix(IDBConnection $connection) { + $result = $connection->executeQuery('SELECT VERSION()'); + $row = strtolower($result->fetchColumn()); + $result->closeCursor(); + + if ($row === false) { + return false; + } + + return strpos($row, 'maria') && version_compare($row, '10.3', '>=') || + strpos($row, 'maria') === false && version_compare($row, '8.0', '>='); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/OCSqlitePlatform.php b/docker/overlays/nextcloud/html/lib/private/DB/OCSqlitePlatform.php new file mode 100644 index 0000000..be33629 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/OCSqlitePlatform.php @@ -0,0 +1,26 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class OCSqlitePlatform extends \Doctrine\DBAL\Platforms\SqlitePlatform { +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/OracleConnection.php b/docker/overlays/nextcloud/html/lib/private/DB/OracleConnection.php new file mode 100644 index 0000000..9dd7620 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/OracleConnection.php @@ -0,0 +1,107 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +class OracleConnection extends Connection { + /** + * Quote the keys of the array + */ + private function quoteKeys(array $data) { + $return = []; + $c = $this->getDatabasePlatform()->getIdentifierQuoteCharacter(); + foreach ($data as $key => $value) { + if ($key[0] !== $c) { + $return[$this->quoteIdentifier($key)] = $value; + } else { + $return[$key] = $value; + } + } + return $return; + } + + /** + * {@inheritDoc} + */ + public function insert($tableExpression, array $data, array $types = []) { + if ($tableExpression[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $tableExpression = $this->quoteIdentifier($tableExpression); + } + $data = $this->quoteKeys($data); + return parent::insert($tableExpression, $data, $types); + } + + /** + * {@inheritDoc} + */ + public function update($tableExpression, array $data, array $identifier, array $types = []) { + if ($tableExpression[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $tableExpression = $this->quoteIdentifier($tableExpression); + } + $data = $this->quoteKeys($data); + $identifier = $this->quoteKeys($identifier); + return parent::update($tableExpression, $data, $identifier, $types); + } + + /** + * {@inheritDoc} + */ + public function delete($tableExpression, array $identifier, array $types = []) { + if ($tableExpression[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $tableExpression = $this->quoteIdentifier($tableExpression); + } + $identifier = $this->quoteKeys($identifier); + return parent::delete($tableExpression, $identifier); + } + + /** + * Drop a table from the database if it exists + * + * @param string $table table name without the prefix + */ + public function dropTable($table) { + $table = $this->tablePrefix . trim($table); + $table = $this->quoteIdentifier($table); + $schema = $this->getSchemaManager(); + if ($schema->tablesExist([$table])) { + $schema->dropTable($table); + } + } + + /** + * Check if a table exists + * + * @param string $table table name without the prefix + * @return bool + */ + public function tableExists($table) { + $table = $this->tablePrefix . trim($table); + $table = $this->quoteIdentifier($table); + $schema = $this->getSchemaManager(); + return $schema->tablesExist([$table]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/OracleMigrator.php b/docker/overlays/nextcloud/html/lib/private/DB/OracleMigrator.php new file mode 100644 index 0000000..cad5390 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/OracleMigrator.php @@ -0,0 +1,232 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Piotr Mrowczynski + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; + +class OracleMigrator extends Migrator { + + /** + * Quote a column's name but changing the name requires recreating + * the column instance and copying over all properties. + * + * @param Column $column old column + * @return Column new column instance with new name + */ + protected function quoteColumn(Column $column) { + $newColumn = new Column( + $this->connection->quoteIdentifier($column->getName()), + $column->getType() + ); + $newColumn->setAutoincrement($column->getAutoincrement()); + $newColumn->setColumnDefinition($column->getColumnDefinition()); + $newColumn->setComment($column->getComment()); + $newColumn->setDefault($column->getDefault()); + $newColumn->setFixed($column->getFixed()); + $newColumn->setLength($column->getLength()); + $newColumn->setNotnull($column->getNotnull()); + $newColumn->setPrecision($column->getPrecision()); + $newColumn->setScale($column->getScale()); + $newColumn->setUnsigned($column->getUnsigned()); + $newColumn->setPlatformOptions($column->getPlatformOptions()); + $newColumn->setCustomSchemaOptions($column->getPlatformOptions()); + return $newColumn; + } + + /** + * Quote an index's name but changing the name requires recreating + * the index instance and copying over all properties. + * + * @param Index $index old index + * @return Index new index instance with new name + */ + protected function quoteIndex($index) { + return new Index( + //TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()), + $index->getName(), + array_map(function ($columnName) { + return $this->connection->quoteIdentifier($columnName); + }, $index->getColumns()), + $index->isUnique(), + $index->isPrimary(), + $index->getFlags(), + $index->getOptions() + ); + } + + /** + * Quote an ForeignKeyConstraint's name but changing the name requires recreating + * the ForeignKeyConstraint instance and copying over all properties. + * + * @param ForeignKeyConstraint $fkc old fkc + * @return ForeignKeyConstraint new fkc instance with new name + */ + protected function quoteForeignKeyConstraint($fkc) { + return new ForeignKeyConstraint( + array_map(function ($columnName) { + return $this->connection->quoteIdentifier($columnName); + }, $fkc->getLocalColumns()), + $this->connection->quoteIdentifier($fkc->getForeignTableName()), + array_map(function ($columnName) { + return $this->connection->quoteIdentifier($columnName); + }, $fkc->getForeignColumns()), + $fkc->getName(), + $fkc->getOptions() + ); + } + + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + * @throws DBALException + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $schemaDiff = parent::getDiff($targetSchema, $connection); + + // oracle forces us to quote the identifiers + $schemaDiff->newTables = array_map(function (Table $table) { + return new Table( + $this->connection->quoteIdentifier($table->getName()), + array_map(function (Column $column) { + return $this->quoteColumn($column); + }, $table->getColumns()), + array_map(function (Index $index) { + return $this->quoteIndex($index); + }, $table->getIndexes()), + array_map(function (ForeignKeyConstraint $fck) { + return $this->quoteForeignKeyConstraint($fck); + }, $table->getForeignKeys()), + 0, + $table->getOptions() + ); + }, $schemaDiff->newTables); + + $schemaDiff->removedTables = array_map(function (Table $table) { + return new Table( + $this->connection->quoteIdentifier($table->getName()), + $table->getColumns(), + $table->getIndexes(), + $table->getForeignKeys(), + 0, + $table->getOptions() + ); + }, $schemaDiff->removedTables); + + foreach ($schemaDiff->changedTables as $tableDiff) { + $tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name); + + $tableDiff->addedColumns = array_map(function (Column $column) { + return $this->quoteColumn($column); + }, $tableDiff->addedColumns); + + foreach ($tableDiff->changedColumns as $column) { + $column->oldColumnName = $this->connection->quoteIdentifier($column->oldColumnName); + // auto increment is not relevant for oracle and can anyhow not be applied on change + $column->changedProperties = array_diff($column->changedProperties, ['autoincrement', 'unsigned']); + } + // remove columns that no longer have changed (because autoincrement and unsigned are not supported) + $tableDiff->changedColumns = array_filter($tableDiff->changedColumns, function (ColumnDiff $column) { + return count($column->changedProperties) > 0; + }); + + $tableDiff->removedColumns = array_map(function (Column $column) { + return $this->quoteColumn($column); + }, $tableDiff->removedColumns); + + $tableDiff->renamedColumns = array_map(function (Column $column) { + return $this->quoteColumn($column); + }, $tableDiff->renamedColumns); + + $tableDiff->addedIndexes = array_map(function (Index $index) { + return $this->quoteIndex($index); + }, $tableDiff->addedIndexes); + + $tableDiff->changedIndexes = array_map(function (Index $index) { + return $this->quoteIndex($index); + }, $tableDiff->changedIndexes); + + $tableDiff->removedIndexes = array_map(function (Index $index) { + return $this->quoteIndex($index); + }, $tableDiff->removedIndexes); + + $tableDiff->renamedIndexes = array_map(function (Index $index) { + return $this->quoteIndex($index); + }, $tableDiff->renamedIndexes); + + $tableDiff->addedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) { + return $this->quoteForeignKeyConstraint($fkc); + }, $tableDiff->addedForeignKeys); + + $tableDiff->changedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) { + return $this->quoteForeignKeyConstraint($fkc); + }, $tableDiff->changedForeignKeys); + + $tableDiff->removedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) { + return $this->quoteForeignKeyConstraint($fkc); + }, $tableDiff->removedForeignKeys); + } + + return $schemaDiff; + } + + /** + * @param string $name + * @return string + */ + protected function generateTemporaryTableName($name) { + return 'oc_' . uniqid(); + } + + /** + * @param $statement + * @return string + */ + protected function convertStatementToScript($statement) { + if (substr($statement, -1) === ';') { + return $statement . PHP_EOL . '/' . PHP_EOL; + } + $script = $statement . ';'; + $script .= PHP_EOL; + $script .= PHP_EOL; + return $script; + } + + protected function getFilterExpression() { + return '/^"' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/PgSqlTools.php b/docker/overlays/nextcloud/html/lib/private/DB/PgSqlTools.php new file mode 100644 index 0000000..724344a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/PgSqlTools.php @@ -0,0 +1,73 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author tbelau666 + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use OCP\IConfig; + +/** + * Various PostgreSQL specific helper functions. + */ +class PgSqlTools { + + /** @var \OCP\IConfig */ + private $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * @brief Resynchronizes all sequences of a database after using INSERTs + * without leaving out the auto-incremented column. + * @param \OC\DB\Connection $conn + * @return null + */ + public function resynchronizeDatabaseSequences(Connection $conn) { + $filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/'; + $databaseName = $conn->getDatabase(); + $conn->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression); + + foreach ($conn->getSchemaManager()->listSequences() as $sequence) { + $sequenceName = $sequence->getName(); + $sqlInfo = 'SELECT table_schema, table_name, column_name + FROM information_schema.columns + WHERE column_default = ? AND table_catalog = ?'; + $sequenceInfo = $conn->fetchAssoc($sqlInfo, [ + "nextval('$sequenceName'::regclass)", + $databaseName + ]); + $tableName = $sequenceInfo['table_name']; + $columnName = $sequenceInfo['column_name']; + $sqlMaxId = "SELECT MAX($columnName) FROM $tableName"; + $sqlSetval = "SELECT setval('$sequenceName', ($sqlMaxId))"; + $conn->executeQuery($sqlSetval); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/PostgreSqlMigrator.php b/docker/overlays/nextcloud/html/lib/private/DB/PostgreSqlMigrator.php new file mode 100644 index 0000000..0c3b795 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/PostgreSqlMigrator.php @@ -0,0 +1,56 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Schema\Schema; + +class PostgreSqlMigrator extends Migrator { + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $schemaDiff = parent::getDiff($targetSchema, $connection); + + foreach ($schemaDiff->changedTables as $tableDiff) { + // fix default value in brackets - pg 9.4 is returning a negative default value in () + // see https://github.com/doctrine/dbal/issues/2427 + foreach ($tableDiff->changedColumns as $column) { + $column->changedProperties = array_filter($column->changedProperties, function ($changedProperties) use ($column) { + if ($changedProperties !== 'default') { + return true; + } + $fromDefault = $column->fromColumn->getDefault(); + $toDefault = $column->column->getDefault(); + $fromDefault = trim($fromDefault, "()"); + + // by intention usage of != + return $fromDefault != $toDefault; + }); + } + } + + return $schemaDiff; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/CompositeExpression.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/CompositeExpression.php new file mode 100644 index 0000000..5123436 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/CompositeExpression.php @@ -0,0 +1,93 @@ + + * @author Joas Schilling + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use OCP\DB\QueryBuilder\ICompositeExpression; + +class CompositeExpression implements ICompositeExpression, \Countable { + /** @var \Doctrine\DBAL\Query\Expression\CompositeExpression */ + protected $compositeExpression; + + /** + * Constructor. + * + * @param \Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression + */ + public function __construct(\Doctrine\DBAL\Query\Expression\CompositeExpression $compositeExpression) { + $this->compositeExpression = $compositeExpression; + } + + /** + * Adds multiple parts to composite expression. + * + * @param array $parts + * + * @return \OCP\DB\QueryBuilder\ICompositeExpression + */ + public function addMultiple(array $parts = []) { + $this->compositeExpression->addMultiple($parts); + + return $this; + } + + /** + * Adds an expression to composite expression. + * + * @param mixed $part + * + * @return \OCP\DB\QueryBuilder\ICompositeExpression + */ + public function add($part) { + $this->compositeExpression->add($part); + + return $this; + } + + /** + * Retrieves the amount of expressions on composite expression. + * + * @return integer + */ + public function count() { + return $this->compositeExpression->count(); + } + + /** + * Returns the type of this composite expression (AND/OR). + * + * @return string + */ + public function getType() { + return $this->compositeExpression->getType(); + } + + /** + * Retrieves the string representation of this composite expression. + * + * @return string + */ + public function __toString() { + return (string) $this->compositeExpression; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php new file mode 100644 index 0000000..f4b4344 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php @@ -0,0 +1,433 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder\ExpressionBuilder; + +use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder; +use OC\DB\QueryBuilder\CompositeExpression; +use OC\DB\QueryBuilder\FunctionBuilder\FunctionBuilder; +use OC\DB\QueryBuilder\Literal; +use OC\DB\QueryBuilder\QueryFunction; +use OC\DB\QueryBuilder\QuoteHelper; +use OCP\DB\QueryBuilder\IExpressionBuilder; +use OCP\DB\QueryBuilder\ILiteral; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; +use OCP\IDBConnection; + +class ExpressionBuilder implements IExpressionBuilder { + /** @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder */ + protected $expressionBuilder; + + /** @var QuoteHelper */ + protected $helper; + + /** @var IDBConnection */ + protected $connection; + + /** @var FunctionBuilder */ + protected $functionBuilder; + + /** + * Initializes a new ExpressionBuilder. + * + * @param IDBConnection $connection + * @param IQueryBuilder $queryBuilder + */ + public function __construct(IDBConnection $connection, IQueryBuilder $queryBuilder) { + $this->connection = $connection; + $this->helper = new QuoteHelper(); + $this->expressionBuilder = new DoctrineExpressionBuilder($connection); + $this->functionBuilder = $queryBuilder->func(); + } + + /** + * Creates a conjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) AND (u.role = ?) + * $expr->andX('u.type = ?', 'u.role = ?')); + * + * @param mixed ...$x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \OCP\DB\QueryBuilder\ICompositeExpression + */ + public function andX(...$x) { + $compositeExpression = call_user_func_array([$this->expressionBuilder, 'andX'], $x); + return new CompositeExpression($compositeExpression); + } + + /** + * Creates a disjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) OR (u.role = ?) + * $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?')); + * + * @param mixed ...$x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \OCP\DB\QueryBuilder\ICompositeExpression + */ + public function orX(...$x) { + $compositeExpression = call_user_func_array([$this->expressionBuilder, 'orX'], $x); + return new CompositeExpression($compositeExpression); + } + + /** + * Creates a comparison expression. + * + * @param mixed $x The left expression. + * @param string $operator One of the IExpressionBuilder::* constants. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function comparison($x, $operator, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->comparison($x, $operator, $y); + } + + /** + * Creates an equality comparison expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a = . Example: + * + * [php] + * // u.id = ? + * $expr->eq('u.id', '?'); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function eq($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->eq($x, $y); + } + + /** + * Creates a non equality comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <> . Example: + * + * [php] + * // u.id <> 1 + * $q->where($q->expr()->neq('u.id', '1')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function neq($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->neq($x, $y); + } + + /** + * Creates a lower-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a < . Example: + * + * [php] + * // u.id < ? + * $q->where($q->expr()->lt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function lt($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->lt($x, $y); + } + + /** + * Creates a lower-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <= . Example: + * + * [php] + * // u.id <= ? + * $q->where($q->expr()->lte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function lte($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->lte($x, $y); + } + + /** + * Creates a greater-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a > . Example: + * + * [php] + * // u.id > ? + * $q->where($q->expr()->gt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function gt($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->gt($x, $y); + } + + /** + * Creates a greater-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a >= . Example: + * + * [php] + * // u.id >= ? + * $q->where($q->expr()->gte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function gte($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->gte($x, $y); + } + + /** + * Creates an IS NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NULL. + * + * @return string + */ + public function isNull($x) { + $x = $this->helper->quoteColumnName($x); + return $this->expressionBuilder->isNull($x); + } + + /** + * Creates an IS NOT NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NOT NULL. + * + * @return string + */ + public function isNotNull($x) { + $x = $this->helper->quoteColumnName($x); + return $this->expressionBuilder->isNotNull($x); + } + + /** + * Creates a LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param mixed $y Argument to be used in LIKE() comparison. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function like($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->like($x, $y); + } + + /** + * Creates a ILIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by ILIKE() comparison. + * @param mixed $y Argument to be used in ILIKE() comparison. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + * @since 9.0.0 + */ + public function iLike($x, $y, $type = null) { + return $this->expressionBuilder->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y)); + } + + /** + * Creates a NOT LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by NOT LIKE() comparison. + * @param mixed $y Argument to be used in NOT LIKE() comparison. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function notLike($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->notLike($x, $y); + } + + /** + * Creates a IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by IN() comparison. + * @param string|array $y The placeholder or the array of values to be used by IN() comparison. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function in($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnNames($y); + return $this->expressionBuilder->in($x, $y); + } + + /** + * Creates a NOT IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by NOT IN() comparison. + * @param string|array $y The placeholder or the array of values to be used by NOT IN() comparison. + * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants + * required when comparing text fields for oci compatibility + * + * @return string + */ + public function notIn($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnNames($y); + return $this->expressionBuilder->notIn($x, $y); + } + + /** + * Creates a $x = '' statement, because Oracle needs a different check + * + * @param string $x The field in string format to be inspected by the comparison. + * @return string + * @since 13.0.0 + */ + public function emptyString($x) { + return $this->eq($x, $this->literal('', IQueryBuilder::PARAM_STR)); + } + + /** + * Creates a `$x <> ''` statement, because Oracle needs a different check + * + * @param string $x The field in string format to be inspected by the comparison. + * @return string + * @since 13.0.0 + */ + public function nonEmptyString($x) { + return $this->neq($x, $this->literal('', IQueryBuilder::PARAM_STR)); + } + + /** + * Binary AND Operator copies a bit to the result if it exists in both operands. + * + * @param string|ILiteral $x The field or value to check + * @param int $y Bitmap that must be set + * @return IQueryFunction + * @since 12.0.0 + */ + public function bitwiseAnd($x, $y) { + return new QueryFunction($this->connection->getDatabasePlatform()->getBitAndComparisonExpression( + $this->helper->quoteColumnName($x), + $y + )); + } + + /** + * Binary OR Operator copies a bit if it exists in either operand. + * + * @param string|ILiteral $x The field or value to check + * @param int $y Bitmap that must be set + * @return IQueryFunction + * @since 12.0.0 + */ + public function bitwiseOr($x, $y) { + return new QueryFunction($this->connection->getDatabasePlatform()->getBitOrComparisonExpression( + $this->helper->quoteColumnName($x), + $y + )); + } + + /** + * Quotes a given input parameter. + * + * @param mixed $input The parameter to be quoted. + * @param mixed|null $type One of the IQueryBuilder::PARAM_* constants + * + * @return ILiteral + */ + public function literal($input, $type = null) { + return new Literal($this->expressionBuilder->literal($input, $type)); + } + + /** + * Returns a IQueryFunction that casts the column to the given type + * + * @param string $column + * @param mixed $type One of IQueryBuilder::PARAM_* + * @return string + */ + public function castColumn($column, $type) { + return new QueryFunction( + $this->helper->quoteColumnName($column) + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php new file mode 100644 index 0000000..899f927 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php @@ -0,0 +1,55 @@ + + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder\ExpressionBuilder; + +use OC\DB\Connection; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class MySqlExpressionBuilder extends ExpressionBuilder { + + /** @var string */ + protected $charset; + + /** + * @param \OCP\IDBConnection|Connection $connection + * @param IQueryBuilder $queryBuilder + */ + public function __construct(IDBConnection $connection, IQueryBuilder $queryBuilder) { + parent::__construct($connection, $queryBuilder); + + $params = $connection->getParams(); + $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + } + + /** + * @inheritdoc + */ + public function iLike($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->charset . '_general_ci LIKE', $y); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php new file mode 100644 index 0000000..2aa007d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php @@ -0,0 +1,192 @@ + + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder\ExpressionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; +use OCP\DB\QueryBuilder\ILiteral; +use OCP\DB\QueryBuilder\IParameter; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; + +class OCIExpressionBuilder extends ExpressionBuilder { + + /** + * @param mixed $column + * @param mixed|null $type + * @return array|IQueryFunction|string + */ + protected function prepareColumn($column, $type) { + if ($type === IQueryBuilder::PARAM_STR && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) { + $column = $this->castColumn($column, $type); + } else { + $column = $this->helper->quoteColumnNames($column); + } + return $column; + } + + /** + * @inheritdoc + */ + public function comparison($x, $operator, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->comparison($x, $operator, $y); + } + + /** + * @inheritdoc + */ + public function eq($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->eq($x, $y); + } + + /** + * @inheritdoc + */ + public function neq($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->neq($x, $y); + } + + /** + * @inheritdoc + */ + public function lt($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->lt($x, $y); + } + + /** + * @inheritdoc + */ + public function lte($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->lte($x, $y); + } + + /** + * @inheritdoc + */ + public function gt($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->gt($x, $y); + } + + /** + * @inheritdoc + */ + public function gte($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->gte($x, $y); + } + + /** + * @inheritdoc + */ + public function in($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->in($x, $y); + } + + /** + * @inheritdoc + */ + public function notIn($x, $y, $type = null) { + $x = $this->prepareColumn($x, $type); + $y = $this->prepareColumn($y, $type); + + return $this->expressionBuilder->notIn($x, $y); + } + + /** + * Creates a $x = '' statement, because Oracle needs a different check + * + * @param string $x The field in string format to be inspected by the comparison. + * @return string + * @since 13.0.0 + */ + public function emptyString($x) { + return $this->isNull($x); + } + + /** + * Creates a `$x <> ''` statement, because Oracle needs a different check + * + * @param string $x The field in string format to be inspected by the comparison. + * @return string + * @since 13.0.0 + */ + public function nonEmptyString($x) { + return $this->isNotNull($x); + } + + /** + * Returns a IQueryFunction that casts the column to the given type + * + * @param string $column + * @param mixed $type One of IQueryBuilder::PARAM_* + * @return IQueryFunction + */ + public function castColumn($column, $type) { + if ($type === IQueryBuilder::PARAM_STR) { + $column = $this->helper->quoteColumnName($column); + return new QueryFunction('to_char(' . $column . ')'); + } + + return parent::castColumn($column, $type); + } + + /** + * @inheritdoc + */ + public function like($x, $y, $type = null) { + return parent::like($x, $y, $type) . " ESCAPE '\\'"; + } + + /** + * @inheritdoc + */ + public function iLike($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return new QueryFunction('REGEXP_LIKE(' . $x . ', \'^\' || REPLACE(REPLACE(' . $y . ', \'%\', \'.*\'), \'_\', \'.\') || \'$\', \'i\')'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php new file mode 100644 index 0000000..141a93f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php @@ -0,0 +1,58 @@ + + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder\ExpressionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class PgSqlExpressionBuilder extends ExpressionBuilder { + + /** + * Returns a IQueryFunction that casts the column to the given type + * + * @param string $column + * @param mixed $type One of IQueryBuilder::PARAM_* + * @return string + */ + public function castColumn($column, $type) { + switch ($type) { + case IQueryBuilder::PARAM_INT: + return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS INT)'); + case IQueryBuilder::PARAM_STR: + return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS TEXT)'); + default: + return parent::castColumn($column, $type); + } + } + + /** + * @inheritdoc + */ + public function iLike($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + return $this->expressionBuilder->comparison($x, 'ILIKE', $y); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php new file mode 100644 index 0000000..1fa0d79 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php @@ -0,0 +1,37 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB\QueryBuilder\ExpressionBuilder; + +class SqliteExpressionBuilder extends ExpressionBuilder { + /** + * @inheritdoc + */ + public function like($x, $y, $type = null) { + return parent::like($x, $y, $type) . " ESCAPE '\\'"; + } + + public function iLike($x, $y, $type = null) { + return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php new file mode 100644 index 0000000..a49edc5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -0,0 +1,97 @@ + + * + * @author Joas Schilling + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB\QueryBuilder\FunctionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; +use OC\DB\QueryBuilder\QuoteHelper; +use OCP\DB\QueryBuilder\IFunctionBuilder; + +class FunctionBuilder implements IFunctionBuilder { + /** @var QuoteHelper */ + protected $helper; + + /** + * ExpressionBuilder constructor. + * + * @param QuoteHelper $helper + */ + public function __construct(QuoteHelper $helper) { + $this->helper = $helper; + } + + public function md5($input) { + return new QueryFunction('MD5(' . $this->helper->quoteColumnName($input) . ')'); + } + + public function concat($x, $y) { + return new QueryFunction('CONCAT(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); + } + + public function substring($input, $start, $length = null) { + if ($length) { + return new QueryFunction('SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($start) . ', ' . $this->helper->quoteColumnName($length) . ')'); + } else { + return new QueryFunction('SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($start) . ')'); + } + } + + public function sum($field) { + return new QueryFunction('SUM(' . $this->helper->quoteColumnName($field) . ')'); + } + + public function lower($field) { + return new QueryFunction('LOWER(' . $this->helper->quoteColumnName($field) . ')'); + } + + public function add($x, $y) { + return new QueryFunction($this->helper->quoteColumnName($x) . ' + ' . $this->helper->quoteColumnName($y)); + } + + public function subtract($x, $y) { + return new QueryFunction($this->helper->quoteColumnName($x) . ' - ' . $this->helper->quoteColumnName($y)); + } + + public function count($count = '', $alias = '') { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $count === '' ? '*' : $this->helper->quoteColumnName($count); + return new QueryFunction('COUNT(' . $quotedName . ')' . $alias); + } + + public function max($field) { + return new QueryFunction('MAX(' . $this->helper->quoteColumnName($field) . ')'); + } + + public function min($field) { + return new QueryFunction('MIN(' . $this->helper->quoteColumnName($field) . ')'); + } + + public function greatest($x, $y) { + return new QueryFunction('GREATEST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); + } + + public function least($x, $y) { + return new QueryFunction('LEAST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php new file mode 100644 index 0000000..f934721 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -0,0 +1,32 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB\QueryBuilder\FunctionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; + +class OCIFunctionBuilder extends FunctionBuilder { + public function md5($input) { + return new QueryFunction('LOWER(DBMS_OBFUSCATION_TOOLKIT.md5 (input => UTL_RAW.cast_to_raw(' . $this->helper->quoteColumnName($input) .')))'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php new file mode 100644 index 0000000..8753d26 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -0,0 +1,33 @@ + + * + * @author Joas Schilling + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB\QueryBuilder\FunctionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; + +class PgSqlFunctionBuilder extends FunctionBuilder { + public function concat($x, $y) { + return new QueryFunction('(' . $this->helper->quoteColumnName($x) . ' || ' . $this->helper->quoteColumnName($y) . ')'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php new file mode 100644 index 0000000..6d8e947 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -0,0 +1,41 @@ + + * + * @author Joas Schilling + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB\QueryBuilder\FunctionBuilder; + +use OC\DB\QueryBuilder\QueryFunction; + +class SqliteFunctionBuilder extends FunctionBuilder { + public function concat($x, $y) { + return new QueryFunction('(' . $this->helper->quoteColumnName($x) . ' || ' . $this->helper->quoteColumnName($y) . ')'); + } + + public function greatest($x, $y) { + return new QueryFunction('MAX(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); + } + + public function least($x, $y) { + return new QueryFunction('MIN(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Literal.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Literal.php new file mode 100644 index 0000000..02fd232 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Literal.php @@ -0,0 +1,42 @@ + + * @author Joas Schilling + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use OCP\DB\QueryBuilder\ILiteral; + +class Literal implements ILiteral { + /** @var mixed */ + protected $literal; + + public function __construct($literal) { + $this->literal = $literal; + } + + /** + * @return string + */ + public function __toString() { + return (string) $this->literal; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Parameter.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Parameter.php new file mode 100644 index 0000000..b64b0cf --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/Parameter.php @@ -0,0 +1,41 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use OCP\DB\QueryBuilder\IParameter; + +class Parameter implements IParameter { + /** @var mixed */ + protected $name; + + public function __construct($name) { + $this->name = $name; + } + + /** + * @return string + */ + public function __toString() { + return (string) $this->name; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryBuilder.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryBuilder.php new file mode 100644 index 0000000..4fde0fb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -0,0 +1,1224 @@ + + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; +use OC\DB\OracleConnection; +use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder; +use OC\DB\QueryBuilder\ExpressionBuilder\MySqlExpressionBuilder; +use OC\DB\QueryBuilder\ExpressionBuilder\OCIExpressionBuilder; +use OC\DB\QueryBuilder\ExpressionBuilder\PgSqlExpressionBuilder; +use OC\DB\QueryBuilder\ExpressionBuilder\SqliteExpressionBuilder; +use OC\DB\QueryBuilder\FunctionBuilder\FunctionBuilder; +use OC\DB\QueryBuilder\FunctionBuilder\OCIFunctionBuilder; +use OC\DB\QueryBuilder\FunctionBuilder\PgSqlFunctionBuilder; +use OC\DB\QueryBuilder\FunctionBuilder\SqliteFunctionBuilder; +use OC\SystemConfig; +use OCP\DB\QueryBuilder\IParameter; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; +use OCP\IDBConnection; +use OCP\ILogger; + +class QueryBuilder implements IQueryBuilder { + + /** @var \OCP\IDBConnection */ + private $connection; + + /** @var SystemConfig */ + private $systemConfig; + + /** @var ILogger */ + private $logger; + + /** @var \Doctrine\DBAL\Query\QueryBuilder */ + private $queryBuilder; + + /** @var QuoteHelper */ + private $helper; + + /** @var bool */ + private $automaticTablePrefix = true; + + /** @var string */ + protected $lastInsertedTable; + + /** + * Initializes a new QueryBuilder. + * + * @param IDBConnection $connection + * @param SystemConfig $systemConfig + * @param ILogger $logger + */ + public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger) { + $this->connection = $connection; + $this->systemConfig = $systemConfig; + $this->logger = $logger; + $this->queryBuilder = new \Doctrine\DBAL\Query\QueryBuilder($this->connection); + $this->helper = new QuoteHelper(); + } + + /** + * Enable/disable automatic prefixing of table names with the oc_ prefix + * + * @param bool $enabled If set to true table names will be prefixed with the + * owncloud database prefix automatically. + * @since 8.2.0 + */ + public function automaticTablePrefix($enabled) { + $this->automaticTablePrefix = (bool) $enabled; + } + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + * + * @return \OCP\DB\QueryBuilder\IExpressionBuilder + */ + public function expr() { + if ($this->connection instanceof OracleConnection) { + return new OCIExpressionBuilder($this->connection, $this); + } elseif ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return new PgSqlExpressionBuilder($this->connection, $this); + } elseif ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) { + return new MySqlExpressionBuilder($this->connection, $this); + } elseif ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + return new SqliteExpressionBuilder($this->connection, $this); + } else { + return new ExpressionBuilder($this->connection, $this); + } + } + + /** + * Gets an FunctionBuilder used for object-oriented construction of query functions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where($qb->fun()->md5('u.id')); + * + * + * For more complex function construction, consider storing the function + * builder object in a local variable. + * + * @return \OCP\DB\QueryBuilder\IFunctionBuilder + */ + public function func() { + if ($this->connection instanceof OracleConnection) { + return new OCIFunctionBuilder($this->helper); + } elseif ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + return new SqliteFunctionBuilder($this->helper); + } elseif ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return new PgSqlFunctionBuilder($this->helper); + } else { + return new FunctionBuilder($this->helper); + } + } + + /** + * Gets the type of the currently built query. + * + * @return integer + */ + public function getType() { + return $this->queryBuilder->getType(); + } + + /** + * Gets the associated DBAL Connection for this query builder. + * + * @return \OCP\IDBConnection + */ + public function getConnection() { + return $this->connection; + } + + /** + * Gets the state of this query builder instance. + * + * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. + */ + public function getState() { + return $this->queryBuilder->getState(); + } + + /** + * Executes this query using the bound parameters and their types. + * + * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate} + * for insert, update and delete statements. + * + * @return \Doctrine\DBAL\Driver\Statement|int + */ + public function execute() { + if ($this->systemConfig->getValue('log_query', false)) { + $params = []; + foreach ($this->getParameters() as $placeholder => $value) { + if (is_array($value)) { + $params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')'; + } else { + $params[] = $placeholder . ' => \'' . $value . '\''; + } + } + if (empty($params)) { + $this->logger->debug('DB QueryBuilder: \'{query}\'', [ + 'query' => $this->getSQL(), + 'app' => 'core', + ]); + } else { + $this->logger->debug('DB QueryBuilder: \'{query}\' with parameters: {params}', [ + 'query' => $this->getSQL(), + 'params' => implode(', ', $params), + 'app' => 'core', + ]); + } + } + + return $this->queryBuilder->execute(); + } + + /** + * Gets the complete SQL string formed by the current specifications of this QueryBuilder. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * echo $qb->getSQL(); // SELECT u FROM User u + * + * + * @return string The SQL query string. + */ + public function getSQL() { + return $this->queryBuilder->getSQL(); + } + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id') + * ->setParameter(':user_id', 1); + * + * + * @param string|integer $key The parameter position or name. + * @param mixed $value The parameter value. + * @param string|null|int $type One of the IQueryBuilder::PARAM_* constants. + * + * @return $this This QueryBuilder instance. + */ + public function setParameter($key, $value, $type = null) { + $this->queryBuilder->setParameter($key, $value, $type); + + return $this; + } + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters(array( + * ':user_id1' => 1, + * ':user_id2' => 2 + * )); + * + * + * @param array $params The query parameters to set. + * @param array $types The query parameters types to set. + * + * @return $this This QueryBuilder instance. + */ + public function setParameters(array $params, array $types = []) { + $this->queryBuilder->setParameters($params, $types); + + return $this; + } + + /** + * Gets all defined query parameters for the query being constructed indexed by parameter index or name. + * + * @return array The currently defined query parameters indexed by parameter index or name. + */ + public function getParameters() { + return $this->queryBuilder->getParameters(); + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter. + * + * @return mixed The value of the bound parameter. + */ + public function getParameter($key) { + return $this->queryBuilder->getParameter($key); + } + + /** + * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. + * + * @return array The currently defined query parameter types indexed by parameter index or name. + */ + public function getParameterTypes() { + return $this->queryBuilder->getParameterTypes(); + } + + /** + * Gets a (previously set) query parameter type of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter type. + * + * @return mixed The value of the bound parameter type. + */ + public function getParameterType($key) { + return $this->queryBuilder->getParameterType($key); + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param integer $firstResult The first result to return. + * + * @return $this This QueryBuilder instance. + */ + public function setFirstResult($firstResult) { + $this->queryBuilder->setFirstResult($firstResult); + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. + * + * @return integer The position of the first result. + */ + public function getFirstResult() { + return $this->queryBuilder->getFirstResult(); + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * NOTE: Setting max results to "0" will cause mixed behaviour. While most + * of the databases will just return an empty result set, Oracle will return + * all entries. + * + * @param integer $maxResults The maximum number of results to retrieve. + * + * @return $this This QueryBuilder instance. + */ + public function setMaxResults($maxResults) { + $this->queryBuilder->setMaxResults($maxResults); + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + * + * @return integer The maximum number of results. + */ + public function getMaxResults() { + return $this->queryBuilder->getMaxResults(); + } + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.id', 'p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); + * + * + * @param mixed ...$selects The selection expressions. + * + * '@return $this This QueryBuilder instance. + */ + public function select(...$selects) { + if (count($selects) === 1 && is_array($selects[0])) { + $selects = $selects[0]; + } + + $this->queryBuilder->select( + $this->helper->quoteColumnNames($selects) + ); + + return $this; + } + + /** + * Specifies an item that is to be returned with a different name in the query result. + * + * + * $qb = $conn->getQueryBuilder() + * ->selectAlias('u.id', 'user_id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); + * + * + * @param mixed $select The selection expressions. + * @param string $alias The column alias used in the constructed query. + * + * @return $this This QueryBuilder instance. + */ + public function selectAlias($select, $alias) { + $this->queryBuilder->addSelect( + $this->helper->quoteColumnName($select) . ' AS ' . $this->helper->quoteColumnName($alias) + ); + + return $this; + } + + /** + * Specifies an item that is to be returned uniquely in the query result. + * + * + * $qb = $conn->getQueryBuilder() + * ->selectDistinct('type') + * ->from('users'); + * + * + * @param mixed $select The selection expressions. + * + * @return $this This QueryBuilder instance. + */ + public function selectDistinct($select) { + $this->queryBuilder->addSelect( + 'DISTINCT ' . $this->helper->quoteColumnName($select) + ); + + return $this; + } + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.id') + * ->addSelect('p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); + * + * + * @param mixed ...$selects The selection expression. + * + * @return $this This QueryBuilder instance. + */ + public function addSelect(...$selects) { + if (count($selects) === 1 && is_array($selects[0])) { + $selects = $selects[0]; + } + + $this->queryBuilder->addSelect( + $this->helper->quoteColumnNames($selects) + ); + + return $this; + } + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain table. + * + * + * $qb = $conn->getQueryBuilder() + * ->delete('users', 'u') + * ->where('u.id = :user_id'); + * ->setParameter(':user_id', 1); + * + * + * @param string $delete The table whose rows are subject to the deletion. + * @param string $alias The table alias used in the constructed query. + * + * @return $this This QueryBuilder instance. + */ + public function delete($delete = null, $alias = null) { + $this->queryBuilder->delete( + $this->getTableName($delete), + $alias + ); + + return $this; + } + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain table + * + * + * $qb = $conn->getQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $update The table whose rows are subject to the update. + * @param string $alias The table alias used in the constructed query. + * + * @return $this This QueryBuilder instance. + */ + public function update($update = null, $alias = null) { + $this->queryBuilder->update( + $this->getTableName($update), + $alias + ); + + return $this; + } + + /** + * Turns the query being built into an insert query that inserts into + * a certain table + * + * + * $qb = $conn->getQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param string $insert The table into which the rows should be inserted. + * + * @return $this This QueryBuilder instance. + */ + public function insert($insert = null) { + $this->queryBuilder->insert( + $this->getTableName($insert) + ); + + $this->lastInsertedTable = $insert; + + return $this; + } + + /** + * Creates and adds a query root corresponding to the table identified by the + * given alias, forming a cartesian product with any existing query roots. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.id') + * ->from('users', 'u') + * + * + * @param string $from The table. + * @param string|null $alias The alias of the table. + * + * @return $this This QueryBuilder instance. + */ + public function from($from, $alias = null) { + $this->queryBuilder->from( + $this->getTableName($from), + $this->quoteAlias($alias) + ); + + return $this; + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function join($fromAlias, $join, $alias, $condition = null) { + $this->queryBuilder->join( + $this->quoteAlias($fromAlias), + $this->getTableName($join), + $this->quoteAlias($alias), + $condition + ); + + return $this; + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function innerJoin($fromAlias, $join, $alias, $condition = null) { + $this->queryBuilder->innerJoin( + $this->quoteAlias($fromAlias), + $this->getTableName($join), + $this->quoteAlias($alias), + $condition + ); + + return $this; + } + + /** + * Creates and adds a left join to the query. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function leftJoin($fromAlias, $join, $alias, $condition = null) { + $this->queryBuilder->leftJoin( + $this->quoteAlias($fromAlias), + $this->getTableName($join), + $this->quoteAlias($alias), + $condition + ); + + return $this; + } + + /** + * Creates and adds a right join to the query. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return $this This QueryBuilder instance. + */ + public function rightJoin($fromAlias, $join, $alias, $condition = null) { + $this->queryBuilder->rightJoin( + $this->quoteAlias($fromAlias), + $this->getTableName($join), + $this->quoteAlias($alias), + $condition + ); + + return $this; + } + + /** + * Sets a new value for a column in a bulk update query. + * + * + * $qb = $conn->getQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $key The column to set. + * @param IParameter|string $value The value, expression, placeholder, etc. + * + * @return $this This QueryBuilder instance. + */ + public function set($key, $value) { + $this->queryBuilder->set( + $this->helper->quoteColumnName($key), + $this->helper->quoteColumnName($value) + ); + + return $this; + } + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programatically build and/or expressions + * $qb = $conn->getQueryBuilder(); + * + * $or = $qb->expr()->orx(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where($or); + * + * + * @param mixed ...$predicates The restriction predicates. + * + * @return $this This QueryBuilder instance. + */ + public function where(...$predicates) { + call_user_func_array( + [$this->queryBuilder, 'where'], + $predicates + ); + + return $this; + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @param mixed ...$where The query restrictions. + * + * @return $this This QueryBuilder instance. + * + * @see where() + */ + public function andWhere(...$where) { + call_user_func_array( + [$this->queryBuilder, 'andWhere'], + $where + ); + + return $this; + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @param mixed ...$where The WHERE statement. + * + * @return $this This QueryBuilder instance. + * + * @see where() + */ + public function orWhere(...$where) { + call_user_func_array( + [$this->queryBuilder, 'orWhere'], + $where + ); + + return $this; + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.id'); + * + * + * @param mixed ...$groupBys The grouping expression. + * + * @return $this This QueryBuilder instance. + */ + public function groupBy(...$groupBys) { + if (count($groupBys) === 1 && is_array($groupBys[0])) { + $groupBys = $groupBys[0]; + } + + call_user_func_array( + [$this->queryBuilder, 'groupBy'], + $this->helper->quoteColumnNames($groupBys) + ); + + return $this; + } + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $conn->getQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.lastLogin'); + * ->addGroupBy('u.createdAt') + * + * + * @param mixed ...$groupBy The grouping expression. + * + * @return $this This QueryBuilder instance. + */ + public function addGroupBy(...$groupBys) { + if (count($groupBys) === 1 && is_array($groupBys[0])) { + $$groupBys = $groupBys[0]; + } + + call_user_func_array( + [$this->queryBuilder, 'addGroupBy'], + $this->helper->quoteColumnNames($groupBys) + ); + + return $this; + } + + /** + * Sets a value for a column in an insert query. + * + * + * $qb = $conn->getQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?' + * ) + * ) + * ->setValue('password', '?'); + * + * + * @param string $column The column into which the value should be inserted. + * @param string $value The value that should be inserted into the column. + * + * @return $this This QueryBuilder instance. + */ + public function setValue($column, $value) { + $this->queryBuilder->setValue( + $this->helper->quoteColumnName($column), + $value + ); + + return $this; + } + + /** + * Specifies values for an insert query indexed by column names. + * Replaces any previous values, if any. + * + * + * $qb = $conn->getQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param array $values The values to specify for the insert query indexed by column names. + * + * @return $this This QueryBuilder instance. + */ + public function values(array $values) { + $quotedValues = []; + foreach ($values as $key => $value) { + $quotedValues[$this->helper->quoteColumnName($key)] = $value; + } + + $this->queryBuilder->values($quotedValues); + + return $this; + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed ...$having The restriction over the groups. + * + * @return $this This QueryBuilder instance. + */ + public function having(...$having) { + call_user_func_array( + [$this->queryBuilder, 'having'], + $having + ); + + return $this; + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed ...$having The restriction to append. + * + * @return $this This QueryBuilder instance. + */ + public function andHaving(...$having) { + call_user_func_array( + [$this->queryBuilder, 'andHaving'], + $having + ); + + return $this; + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed ...$having The restriction to add. + * + * @return $this This QueryBuilder instance. + */ + public function orHaving(...$having) { + call_user_func_array( + [$this->queryBuilder, 'orHaving'], + $having + ); + + return $this; + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return $this This QueryBuilder instance. + */ + public function orderBy($sort, $order = null) { + $this->queryBuilder->orderBy( + $this->helper->quoteColumnName($sort), + $order + ); + + return $this; + } + + /** + * Adds an ordering to the query results. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return $this This QueryBuilder instance. + */ + public function addOrderBy($sort, $order = null) { + $this->queryBuilder->addOrderBy( + $this->helper->quoteColumnName($sort), + $order + ); + + return $this; + } + + /** + * Gets a query part by its name. + * + * @param string $queryPartName + * + * @return mixed + */ + public function getQueryPart($queryPartName) { + return $this->queryBuilder->getQueryPart($queryPartName); + } + + /** + * Gets all query parts. + * + * @return array + */ + public function getQueryParts() { + return $this->queryBuilder->getQueryParts(); + } + + /** + * Resets SQL parts. + * + * @param array|null $queryPartNames + * + * @return $this This QueryBuilder instance. + */ + public function resetQueryParts($queryPartNames = null) { + $this->queryBuilder->resetQueryParts($queryPartNames); + + return $this; + } + + /** + * Resets a single SQL part. + * + * @param string $queryPartName + * + * @return $this This QueryBuilder instance. + */ + public function resetQueryPart($queryPartName) { + $this->queryBuilder->resetQueryPart($queryPartName); + + return $this; + } + + /** + * Creates a new named parameter and bind the value $value to it. + * + * This method provides a shortcut for PDOStatement::bindValue + * when using prepared statements. + * + * The parameter $value specifies the value that you want to bind. If + * $placeholder is not provided bindValue() will automatically create a + * placeholder for you. An automatic placeholder will be of the name + * ':dcValue1', ':dcValue2' etc. + * + * For more information see {@link http://php.net/pdostatement-bindparam} + * + * Example: + * + * $value = 2; + * $q->eq( 'id', $q->bindValue( $value ) ); + * $stmt = $q->executeQuery(); // executed with 'id = 2' + * + * + * @license New BSD License + * @link http://www.zetacomponents.org + * + * @param mixed $value + * @param mixed $type + * @param string $placeHolder The name to bind with. The string must start with a colon ':'. + * + * @return IParameter the placeholder name used. + */ + public function createNamedParameter($value, $type = IQueryBuilder::PARAM_STR, $placeHolder = null) { + return new Parameter($this->queryBuilder->createNamedParameter($value, $type, $placeHolder)); + } + + /** + * Creates a new positional parameter and bind the given value to it. + * + * Attention: If you are using positional parameters with the query builder you have + * to be very careful to bind all parameters in the order they appear in the SQL + * statement , otherwise they get bound in the wrong order which can lead to serious + * bugs in your code. + * + * Example: + * + * $qb = $conn->getQueryBuilder(); + * $qb->select('u.*') + * ->from('users', 'u') + * ->where('u.username = ' . $qb->createPositionalParameter('Foo', IQueryBuilder::PARAM_STR)) + * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', IQueryBuilder::PARAM_STR)) + * + * + * @param mixed $value + * @param integer $type + * + * @return IParameter + */ + public function createPositionalParameter($value, $type = IQueryBuilder::PARAM_STR) { + return new Parameter($this->queryBuilder->createPositionalParameter($value, $type)); + } + + /** + * Creates a new parameter + * + * Example: + * + * $qb = $conn->getQueryBuilder(); + * $qb->select('u.*') + * ->from('users', 'u') + * ->where('u.username = ' . $qb->createParameter('name')) + * ->setParameter('name', 'Bar', IQueryBuilder::PARAM_STR)) + * + * + * @param string $name + * + * @return IParameter + */ + public function createParameter($name) { + return new Parameter(':' . $name); + } + + /** + * Creates a new function + * + * Attention: Column names inside the call have to be quoted before hand + * + * Example: + * + * $qb = $conn->getQueryBuilder(); + * $qb->select($qb->createFunction('COUNT(*)')) + * ->from('users', 'u') + * echo $qb->getSQL(); // SELECT COUNT(*) FROM `users` u + * + * + * $qb = $conn->getQueryBuilder(); + * $qb->select($qb->createFunction('COUNT(`column`)')) + * ->from('users', 'u') + * echo $qb->getSQL(); // SELECT COUNT(`column`) FROM `users` u + * + * + * @param string $call + * + * @return IQueryFunction + */ + public function createFunction($call) { + return new QueryFunction($call); + } + + /** + * Used to get the id of the last inserted element + * @return int + * @throws \BadMethodCallException When being called before an insert query has been run. + */ + public function getLastInsertId() { + if ($this->getType() === \Doctrine\DBAL\Query\QueryBuilder::INSERT && $this->lastInsertedTable) { + // lastInsertId() needs the prefix but no quotes + $table = $this->prefixTableName($this->lastInsertedTable); + return (int) $this->connection->lastInsertId($table); + } + + throw new \BadMethodCallException('Invalid call to getLastInsertId without using insert() before.'); + } + + /** + * Returns the table name quoted and with database prefix as needed by the implementation + * + * @param string $table + * @return string + */ + public function getTableName($table) { + if ($table instanceof IQueryFunction) { + return (string) $table; + } + + $table = $this->prefixTableName($table); + return $this->helper->quoteColumnName($table); + } + + /** + * Returns the table name with database prefix as needed by the implementation + * + * @param string $table + * @return string + */ + protected function prefixTableName($table) { + if ($this->automaticTablePrefix === false || strpos($table, '*PREFIX*') === 0) { + return $table; + } + + return '*PREFIX*' . $table; + } + + /** + * Returns the column name quoted and with table alias prefix as needed by the implementation + * + * @param string $column + * @param string $tableAlias + * @return string + */ + public function getColumnName($column, $tableAlias = '') { + if ($tableAlias !== '') { + $tableAlias .= '.'; + } + + return $this->helper->quoteColumnName($tableAlias . $column); + } + + /** + * Returns the column name quoted and with table alias prefix as needed by the implementation + * + * @param string $alias + * @return string + */ + public function quoteAlias($alias) { + if ($alias === '' || $alias === null) { + return $alias; + } + + return $this->helper->quoteColumnName($alias); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryFunction.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryFunction.php new file mode 100644 index 0000000..60f2bf1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QueryFunction.php @@ -0,0 +1,41 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use OCP\DB\QueryBuilder\IQueryFunction; + +class QueryFunction implements IQueryFunction { + /** @var string */ + protected $function; + + public function __construct($function) { + $this->function = $function; + } + + /** + * @return string + */ + public function __toString() { + return (string) $this->function; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QuoteHelper.php b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QuoteHelper.php new file mode 100644 index 0000000..9f31e69 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/QueryBuilder/QuoteHelper.php @@ -0,0 +1,82 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB\QueryBuilder; + +use OCP\DB\QueryBuilder\ILiteral; +use OCP\DB\QueryBuilder\IParameter; +use OCP\DB\QueryBuilder\IQueryFunction; + +class QuoteHelper { + /** + * @param array|string|ILiteral|IParameter|IQueryFunction $strings string, Literal or Parameter + * @return array|string + */ + public function quoteColumnNames($strings) { + if (!is_array($strings)) { + return $this->quoteColumnName($strings); + } + + $return = []; + foreach ($strings as $string) { + $return[] = $this->quoteColumnName($string); + } + + return $return; + } + + /** + * @param string|ILiteral|IParameter|IQueryFunction $string string, Literal or Parameter + * @return string + */ + public function quoteColumnName($string) { + if ($string instanceof IParameter || $string instanceof ILiteral || $string instanceof IQueryFunction) { + return (string) $string; + } + + if ($string === null || $string === 'null' || $string === '*') { + return $string; + } + + if (!is_string($string)) { + throw new \InvalidArgumentException('Only strings, Literals and Parameters are allowed'); + } + + $string = str_replace(' AS ', ' as ', $string); + if (substr_count($string, ' as ')) { + return implode(' as ', array_map([$this, 'quoteColumnName'], explode(' as ', $string, 2))); + } + + if (substr_count($string, '.')) { + list($alias, $columnName) = explode('.', $string, 2); + + if ($columnName === '*') { + return '`' . $alias . '`.*'; + } + + return '`' . $alias . '`.`' . $columnName . '`'; + } + + return '`' . $string . '`'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/ReconnectWrapper.php b/docker/overlays/nextcloud/html/lib/private/DB/ReconnectWrapper.php new file mode 100644 index 0000000..9599d6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/ReconnectWrapper.php @@ -0,0 +1,55 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB; + +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Driver; + +class ReconnectWrapper extends \Doctrine\DBAL\Connection { + public const CHECK_CONNECTION_INTERVAL = 60; + + private $lastConnectionCheck = null; + + public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) { + parent::__construct($params, $driver, $config, $eventManager); + $this->lastConnectionCheck = time(); + } + + public function connect() { + $now = time(); + $checkTime = $now - self::CHECK_CONNECTION_INTERVAL; + + if ($this->lastConnectionCheck > $checkTime || $this->isTransactionActive()) { + return parent::connect(); + } else { + $this->lastConnectionCheck = $now; + if (!$this->ping()) { + $this->close(); + } + return parent::connect(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/SQLiteMigrator.php b/docker/overlays/nextcloud/html/lib/private/DB/SQLiteMigrator.php new file mode 100644 index 0000000..16f18be --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/SQLiteMigrator.php @@ -0,0 +1,94 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\BigIntType; +use Doctrine\DBAL\Types\Type; + +class SQLiteMigrator extends Migrator { + + /** + * @param \Doctrine\DBAL\Schema\Schema $targetSchema + * @throws \OC\DB\MigrationException + * + * For sqlite we simple make a copy of the entire database, and test the migration on that + */ + public function checkMigrate(\Doctrine\DBAL\Schema\Schema $targetSchema) { + $dbFile = $this->connection->getDatabase(); + $tmpFile = $this->buildTempDatabase(); + copy($dbFile, $tmpFile); + + $connectionParams = [ + 'path' => $tmpFile, + 'driver' => 'pdo_sqlite', + ]; + $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams); + try { + $this->applySchema($targetSchema, $conn); + $conn->close(); + unlink($tmpFile); + } catch (DBALException $e) { + $conn->close(); + unlink($tmpFile); + throw new MigrationException('', $e->getMessage()); + } + } + + /** + * @return string + */ + private function buildTempDatabase() { + $dataDir = $this->config->getSystemValue("datadirectory", \OC::$SERVERROOT . '/data'); + $tmpFile = uniqid("oc_"); + return "$dataDir/$tmpFile.db"; + } + + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $platform = $connection->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('tinyint unsigned', 'integer'); + $platform->registerDoctrineTypeMapping('smallint unsigned', 'integer'); + $platform->registerDoctrineTypeMapping('varchar ', 'string'); + + // with sqlite autoincrement columns is of type integer + foreach ($targetSchema->getTables() as $table) { + foreach ($table->getColumns() as $column) { + if ($column->getType() instanceof BigIntType && $column->getAutoincrement()) { + $column->setType(Type::getType('integer')); + } + } + } + + return parent::getDiff($targetSchema, $connection); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/SQLiteSessionInit.php b/docker/overlays/nextcloud/html/lib/private/DB/SQLiteSessionInit.php new file mode 100644 index 0000000..0c53a05 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/SQLiteSessionInit.php @@ -0,0 +1,71 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\DB; + +use Doctrine\Common\EventSubscriber; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; + +class SQLiteSessionInit implements EventSubscriber { + /** + * @var bool + */ + private $caseSensitiveLike; + + /** + * @var string + */ + private $journalMode; + + /** + * Configure case sensitive like for each connection + * + * @param bool $caseSensitiveLike + * @param string $journalMode + */ + public function __construct($caseSensitiveLike, $journalMode) { + $this->caseSensitiveLike = $caseSensitiveLike; + $this->journalMode = $journalMode; + } + + /** + * @param ConnectionEventArgs $args + * @return void + */ + public function postConnect(ConnectionEventArgs $args) { + $sensitive = $this->caseSensitiveLike ? 'true' : 'false'; + $args->getConnection()->executeUpdate('PRAGMA case_sensitive_like = ' . $sensitive); + $args->getConnection()->executeUpdate('PRAGMA journal_mode = ' . $this->journalMode); + /** @var \PDO $pdo */ + $pdo = $args->getConnection()->getWrappedConnection(); + $pdo->sqliteCreateFunction('md5', 'md5', 1); + } + + public function getSubscribedEvents() { + return [Events::postConnect]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/SchemaWrapper.php b/docker/overlays/nextcloud/html/lib/private/DB/SchemaWrapper.php new file mode 100644 index 0000000..e42535d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/SchemaWrapper.php @@ -0,0 +1,136 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Schema\Schema; +use OCP\DB\ISchemaWrapper; +use OCP\IDBConnection; + +class SchemaWrapper implements ISchemaWrapper { + + /** @var IDBConnection|Connection */ + protected $connection; + + /** @var Schema */ + protected $schema; + + /** @var array */ + protected $tablesToDelete = []; + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + $this->schema = $this->connection->createSchema(); + } + + public function getWrappedSchema() { + return $this->schema; + } + + public function performDropTableCalls() { + foreach ($this->tablesToDelete as $tableName => $true) { + $this->connection->dropTable($tableName); + unset($this->tablesToDelete[$tableName]); + } + } + + /** + * Gets all table names + * + * @return array + */ + public function getTableNamesWithoutPrefix() { + $tableNames = $this->schema->getTableNames(); + return array_map(function ($tableName) { + if (strpos($tableName, $this->connection->getPrefix()) === 0) { + return substr($tableName, strlen($this->connection->getPrefix())); + } + + return $tableName; + }, $tableNames); + } + + // Overwritten methods + + /** + * @return array + */ + public function getTableNames() { + return $this->schema->getTableNames(); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function getTable($tableName) { + return $this->schema->getTable($this->connection->getPrefix() . $tableName); + } + + /** + * Does this schema have a table with the given name? + * + * @param string $tableName + * + * @return boolean + */ + public function hasTable($tableName) { + return $this->schema->hasTable($this->connection->getPrefix() . $tableName); + } + + /** + * Creates a new table. + * + * @param string $tableName + * @return \Doctrine\DBAL\Schema\Table + */ + public function createTable($tableName) { + return $this->schema->createTable($this->connection->getPrefix() . $tableName); + } + + /** + * Drops a table from the schema. + * + * @param string $tableName + * @return \Doctrine\DBAL\Schema\Schema + */ + public function dropTable($tableName) { + $this->tablesToDelete[$tableName] = true; + return $this->schema->dropTable($this->connection->getPrefix() . $tableName); + } + + /** + * Gets all tables of this schema. + * + * @return \Doctrine\DBAL\Schema\Table[] + */ + public function getTables() { + return $this->schema->getTables(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DB/SetTransactionIsolationLevel.php b/docker/overlays/nextcloud/html/lib/private/DB/SetTransactionIsolationLevel.php new file mode 100644 index 0000000..eb15bf1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DB/SetTransactionIsolationLevel.php @@ -0,0 +1,46 @@ + + * + * @author Daniel Kesselberg + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DB; + +use Doctrine\Common\EventSubscriber; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\DBAL\TransactionIsolationLevel; + +class SetTransactionIsolationLevel implements EventSubscriber { + /** + * @param ConnectionEventArgs $args + * @return void + */ + public function postConnect(ConnectionEventArgs $args) { + $args->getConnection()->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); + } + + public function getSubscribedEvents() { + return [Events::postConnect]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Dashboard/DashboardManager.php b/docker/overlays/nextcloud/html/lib/private/Dashboard/DashboardManager.php new file mode 100644 index 0000000..4501e3c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Dashboard/DashboardManager.php @@ -0,0 +1,140 @@ + + * + * @author Maxence Lange + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Dashboard; + +use OCP\Dashboard\Exceptions\DashboardAppNotAvailableException; +use OCP\Dashboard\IDashboardManager; +use OCP\Dashboard\Model\IWidgetConfig; +use OCP\Dashboard\Service\IEventsService; +use OCP\Dashboard\Service\IWidgetsService; + +/** + * Class DashboardManager + * + * @package OC\Dashboard + */ +class DashboardManager implements IDashboardManager { + + + /** @var IWidgetsService */ + private $widgetsService; + + /** @var IEventsService */ + private $eventsService; + + + /** + * @param IEventsService $eventsService + */ + public function registerEventsService(IEventsService $eventsService) { + $this->eventsService = $eventsService; + } + + + /** + * @param IWidgetsService $widgetsService + */ + public function registerWidgetsService(IWidgetsService $widgetsService) { + $this->widgetsService = $widgetsService; + } + + + /** + * @param string $widgetId + * @param string $userId + * + * @return IWidgetConfig + * @throws DashboardAppNotAvailableException + */ + public function getWidgetConfig(string $widgetId, string $userId): IWidgetConfig { + return $this->getWidgetsService()->getWidgetConfig($widgetId, $userId); + } + + + /** + * @param string $widgetId + * @param array $users + * @param array $payload + * @param string $uniqueId + * + * @throws DashboardAppNotAvailableException + */ + public function createUsersEvent(string $widgetId, array $users, array $payload, string $uniqueId = '') { + $this->getEventsService()->createUsersEvent($widgetId, $users, $payload, $uniqueId); + } + + + /** + * @param string $widgetId + * @param array $groups + * @param array $payload + * @param string $uniqueId + * + * @throws DashboardAppNotAvailableException + */ + public function createGroupsEvent(string $widgetId, array $groups, array $payload, string $uniqueId = '') { + $this->getEventsService()->createGroupsEvent($widgetId, $groups, $payload, $uniqueId); + } + + + /** + * @param string $widgetId + * @param array $payload + * @param string $uniqueId + * + * @throws DashboardAppNotAvailableException + */ + public function createGlobalEvent(string $widgetId, array $payload, string $uniqueId = '') { + $this->getEventsService()->createGlobalEvent($widgetId, $payload, $uniqueId); + } + + + /** + * @return IWidgetsService + * @throws DashboardAppNotAvailableException + */ + private function getWidgetsService() { + if ($this->widgetsService === null) { + throw new DashboardAppNotAvailableException('No IWidgetsService registered'); + } + + return $this->widgetsService; + } + + + /** + * @return IEventsService + * @throws DashboardAppNotAvailableException + */ + private function getEventsService() { + if ($this->eventsService === null) { + throw new DashboardAppNotAvailableException('No IEventsService registered'); + } + + return $this->eventsService; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Dashboard/Manager.php b/docker/overlays/nextcloud/html/lib/private/Dashboard/Manager.php new file mode 100644 index 0000000..94b80a3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Dashboard/Manager.php @@ -0,0 +1,122 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Dashboard; + +use InvalidArgumentException; +use OCP\AppFramework\QueryException; +use OCP\Dashboard\IManager; +use OCP\Dashboard\IWidget; +use OCP\ILogger; +use OCP\IServerContainer; +use Throwable; + +class Manager implements IManager { + + /** @var array */ + private $lazyWidgets = []; + + /** @var IWidget[] */ + private $widgets = []; + + /** @var IServerContainer */ + private $serverContainer; + + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + private function registerWidget(IWidget $widget): void { + if (array_key_exists($widget->getId(), $this->widgets)) { + throw new InvalidArgumentException('Dashboard widget with this id has already been registered'); + } + + $this->widgets[$widget->getId()] = $widget; + } + + public function lazyRegisterWidget(string $widgetClass): void { + $this->lazyWidgets[] = $widgetClass; + } + + public function loadLazyPanels(): void { + $classes = $this->lazyWidgets; + foreach ($classes as $class) { + try { + /** @var IWidget $widget */ + $widget = $this->serverContainer->query($class); + } catch (QueryException $e) { + /* + * There is a circular dependency between the logger and the registry, so + * we can not inject it. Thus the static call. + */ + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Could not load lazy dashbaord widget: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + /** + * Try to register the loaded reporter. Theoretically it could be of a wrong + * type, so we might get a TypeError here that we should catch. + */ + try { + $this->registerWidget($widget); + } catch (Throwable $e) { + /* + * There is a circular dependency between the logger and the registry, so + * we can not inject it. Thus the static call. + */ + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Could not register lazy dashboard widget: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + + try { + $startTime = microtime(true); + $widget->load(); + $endTime = microtime(true); + $duration = $endTime - $startTime; + if ($duration > 1) { + \OC::$server->getLogger()->error('Dashboard widget {widget} took {duration} seconds to load.', [ + 'widget' => $widget->getId(), + 'duration' => round($duration, 2), + ]); + } + } catch (Throwable $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Error during dashboard widget loading: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + } + $this->lazyWidgets = []; + } + + public function getWidgets(): array { + $this->loadLazyPanels(); + return $this->widgets; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DatabaseException.php b/docker/overlays/nextcloud/html/lib/private/DatabaseException.php new file mode 100644 index 0000000..6338bd1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DatabaseException.php @@ -0,0 +1,27 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class DatabaseException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/DatabaseSetupException.php b/docker/overlays/nextcloud/html/lib/private/DatabaseSetupException.php new file mode 100644 index 0000000..2fefb92 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DatabaseSetupException.php @@ -0,0 +1,27 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class DatabaseSetupException extends HintException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/DateTimeFormatter.php b/docker/overlays/nextcloud/html/lib/private/DateTimeFormatter.php new file mode 100644 index 0000000..45a1cbd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DateTimeFormatter.php @@ -0,0 +1,319 @@ + + * @author dartcafe + * @author Joas Schilling + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class DateTimeFormatter implements \OCP\IDateTimeFormatter { + /** @var \DateTimeZone */ + protected $defaultTimeZone; + + /** @var \OCP\IL10N */ + protected $defaultL10N; + + /** + * Constructor + * + * @param \DateTimeZone $defaultTimeZone Set the timezone for the format + * @param \OCP\IL10N $defaultL10N Set the language for the format + */ + public function __construct(\DateTimeZone $defaultTimeZone, \OCP\IL10N $defaultL10N) { + $this->defaultTimeZone = $defaultTimeZone; + $this->defaultL10N = $defaultL10N; + } + + /** + * Get TimeZone to use + * + * @param \DateTimeZone $timeZone The timezone to use + * @return \DateTimeZone The timezone to use, falling back to the current user's timezone + */ + protected function getTimeZone($timeZone = null) { + if ($timeZone === null) { + $timeZone = $this->defaultTimeZone; + } + + return $timeZone; + } + + /** + * Get \OCP\IL10N to use + * + * @param \OCP\IL10N $l The locale to use + * @return \OCP\IL10N The locale to use, falling back to the current user's locale + */ + protected function getLocale($l = null) { + if ($l === null) { + $l = $this->defaultL10N; + } + + return $l; + } + + /** + * Generates a DateTime object with the given timestamp and TimeZone + * + * @param mixed $timestamp + * @param \DateTimeZone $timeZone The timezone to use + * @return \DateTime + */ + protected function getDateTime($timestamp, \DateTimeZone $timeZone = null) { + if ($timestamp === null) { + return new \DateTime('now', $timeZone); + } elseif (!$timestamp instanceof \DateTime) { + $dateTime = new \DateTime('now', $timeZone); + $dateTime->setTimestamp($timestamp); + return $dateTime; + } + if ($timeZone) { + $timestamp->setTimezone($timeZone); + } + return $timestamp; + } + + /** + * Formats the date of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' + * long: e.g. 'MMMM d, y' => 'August 20, 2014' + * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' + * short: e.g. 'M/d/yy' => '8/20/14' + * The exact format is dependent on the language + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted date string + */ + public function formatDate($timestamp, $format = 'long', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + return $this->format($timestamp, 'date', $format, $timeZone, $l); + } + + /** + * Formats the date of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' + * long: e.g. 'MMMM d, y' => 'August 20, 2014' + * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' + * short: e.g. 'M/d/yy' => '8/20/14' + * The exact format is dependent on the language + * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted relative date string + */ + public function formatDateRelativeDay($timestamp, $format = 'long', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + if (substr($format, -1) !== '*' && substr($format, -1) !== '*') { + $format .= '^'; + } + + return $this->format($timestamp, 'date', $format, $timeZone, $l); + } + + /** + * Gives the relative date of the timestamp + * Only works for past dates + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param int|\DateTime $baseTimestamp Timestamp to compare $timestamp against, defaults to current time + * @return string Dates returned are: + * < 1 month => Today, Yesterday, n days ago + * < 13 month => last month, n months ago + * >= 13 month => last year, n years ago + * @param \OCP\IL10N $l The locale to use + * @return string Formatted date span + */ + public function formatDateSpan($timestamp, $baseTimestamp = null, \OCP\IL10N $l = null) { + $l = $this->getLocale($l); + $timestamp = $this->getDateTime($timestamp); + $timestamp->setTime(0, 0, 0); + + if ($baseTimestamp === null) { + $baseTimestamp = time(); + } + $baseTimestamp = $this->getDateTime($baseTimestamp); + $baseTimestamp->setTime(0, 0, 0); + $dateInterval = $timestamp->diff($baseTimestamp); + + if ($dateInterval->y == 0 && $dateInterval->m == 0 && $dateInterval->d == 0) { + return $l->t('today'); + } elseif ($dateInterval->y == 0 && $dateInterval->m == 0 && $dateInterval->d == 1) { + if ($timestamp > $baseTimestamp) { + return $l->t('tomorrow'); + } else { + return $l->t('yesterday'); + } + } elseif ($dateInterval->y == 0 && $dateInterval->m == 0) { + if ($timestamp > $baseTimestamp) { + return $l->n('in %n day', 'in %n days', $dateInterval->d); + } else { + return $l->n('%n day ago', '%n days ago', $dateInterval->d); + } + } elseif ($dateInterval->y == 0 && $dateInterval->m == 1) { + if ($timestamp > $baseTimestamp) { + return $l->t('next month'); + } else { + return $l->t('last month'); + } + } elseif ($dateInterval->y == 0) { + if ($timestamp > $baseTimestamp) { + return $l->n('in %n month', 'in %n months', $dateInterval->m); + } else { + return $l->n('%n month ago', '%n months ago', $dateInterval->m); + } + } elseif ($dateInterval->y == 1) { + if ($timestamp > $baseTimestamp) { + return $l->t('next year'); + } else { + return $l->t('last year'); + } + } + if ($timestamp > $baseTimestamp) { + return $l->n('in %n year', 'in %n years', $dateInterval->y); + } else { + return $l->n('%n year ago', '%n years ago', $dateInterval->y); + } + } + + /** + * Formats the time of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'h:mm:ss a zzzz' => '11:42:13 AM GMT+0:00' + * long: e.g. 'h:mm:ss a z' => '11:42:13 AM GMT' + * medium: e.g. 'h:mm:ss a' => '11:42:13 AM' + * short: e.g. 'h:mm a' => '11:42 AM' + * The exact format is dependent on the language + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted time string + */ + public function formatTime($timestamp, $format = 'medium', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + return $this->format($timestamp, 'time', $format, $timeZone, $l); + } + + /** + * Gives the relative past time of the timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param int|\DateTime $baseTimestamp Timestamp to compare $timestamp against, defaults to current time + * @return string Dates returned are: + * < 60 sec => seconds ago + * < 1 hour => n minutes ago + * < 1 day => n hours ago + * < 1 month => Yesterday, n days ago + * < 13 month => last month, n months ago + * >= 13 month => last year, n years ago + * @param \OCP\IL10N $l The locale to use + * @return string Formatted time span + */ + public function formatTimeSpan($timestamp, $baseTimestamp = null, \OCP\IL10N $l = null) { + $l = $this->getLocale($l); + $timestamp = $this->getDateTime($timestamp); + if ($baseTimestamp === null) { + $baseTimestamp = time(); + } + $baseTimestamp = $this->getDateTime($baseTimestamp); + + $diff = $timestamp->diff($baseTimestamp); + if ($diff->y > 0 || $diff->m > 0 || $diff->d > 0) { + return $this->formatDateSpan($timestamp, $baseTimestamp, $l); + } + + if ($diff->h > 0) { + if ($timestamp > $baseTimestamp) { + return $l->n('in %n hour', 'in %n hours', $diff->h); + } else { + return $l->n('%n hour ago', '%n hours ago', $diff->h); + } + } elseif ($diff->i > 0) { + if ($timestamp > $baseTimestamp) { + return $l->n('in %n minute', 'in %n minutes', $diff->i); + } else { + return $l->n('%n minute ago', '%n minutes ago', $diff->i); + } + } + if ($timestamp > $baseTimestamp) { + return $l->t('in a few seconds'); + } else { + return $l->t('seconds ago'); + } + } + + /** + * Formats the date and time of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $formatDate See formatDate() for description + * @param string $formatTime See formatTime() for description + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted date and time string + */ + public function formatDateTime($timestamp, $formatDate = 'long', $formatTime = 'medium', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + return $this->format($timestamp, 'datetime', $formatDate . '|' . $formatTime, $timeZone, $l); + } + + /** + * Formats the date and time of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $formatDate See formatDate() for description + * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable + * @param string $formatTime See formatTime() for description + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted relative date and time string + */ + public function formatDateTimeRelativeDay($timestamp, $formatDate = 'long', $formatTime = 'medium', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + if (substr($formatDate, -1) !== '^' && substr($formatDate, -1) !== '*') { + $formatDate .= '^'; + } + + return $this->format($timestamp, 'datetime', $formatDate . '|' . $formatTime, $timeZone, $l); + } + + /** + * Formats the date and time of the given timestamp + * + * @param int|\DateTime $timestamp Either a Unix timestamp or DateTime object + * @param string $type One of 'date', 'datetime' or 'time' + * @param string $format Format string + * @param \DateTimeZone $timeZone The timezone to use + * @param \OCP\IL10N $l The locale to use + * @return string Formatted date and time string + */ + protected function format($timestamp, $type, $format, \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) { + $l = $this->getLocale($l); + $timeZone = $this->getTimeZone($timeZone); + $timestamp = $this->getDateTime($timestamp, $timeZone); + + return $l->l($type, $timestamp, [ + 'width' => $format, + ]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DateTimeZone.php b/docker/overlays/nextcloud/html/lib/private/DateTimeZone.php new file mode 100644 index 0000000..7bb4a86 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DateTimeZone.php @@ -0,0 +1,130 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\IConfig; +use OCP\IDateTimeZone; +use OCP\ILogger; +use OCP\ISession; + +class DateTimeZone implements IDateTimeZone { + /** @var IConfig */ + protected $config; + + /** @var ISession */ + protected $session; + + /** + * Constructor + * + * @param IConfig $config + * @param ISession $session + */ + public function __construct(IConfig $config, ISession $session) { + $this->config = $config; + $this->session = $session; + } + + /** + * Get the timezone of the current user, based on his session information and config data + * + * @param bool|int $timestamp + * @return \DateTimeZone + */ + public function getTimeZone($timestamp = false) { + $timeZone = $this->config->getUserValue($this->session->get('user_id'), 'core', 'timezone', null); + if ($timeZone === null) { + if ($this->session->exists('timezone')) { + return $this->guessTimeZoneFromOffset($this->session->get('timezone'), $timestamp); + } + $timeZone = $this->getDefaultTimeZone(); + } + + try { + return new \DateTimeZone($timeZone); + } catch (\Exception $e) { + \OCP\Util::writeLog('datetimezone', 'Failed to created DateTimeZone "' . $timeZone . "'", ILogger::DEBUG); + return new \DateTimeZone($this->getDefaultTimeZone()); + } + } + + /** + * Guess the DateTimeZone for a given offset + * + * We first try to find a Etc/GMT* timezone, if that does not exist, + * we try to find it manually, before falling back to UTC. + * + * @param mixed $offset + * @param bool|int $timestamp + * @return \DateTimeZone + */ + protected function guessTimeZoneFromOffset($offset, $timestamp) { + try { + // Note: the timeZone name is the inverse to the offset, + // so a positive offset means negative timeZone + // and the other way around. + if ($offset > 0) { + $timeZone = 'Etc/GMT-' . $offset; + } else { + $timeZone = 'Etc/GMT+' . abs($offset); + } + + return new \DateTimeZone($timeZone); + } catch (\Exception $e) { + // If the offset has no Etc/GMT* timezone, + // we try to guess one timezone that has the same offset + foreach (\DateTimeZone::listIdentifiers() as $timeZone) { + $dtz = new \DateTimeZone($timeZone); + $dateTime = new \DateTime(); + + if ($timestamp !== false) { + $dateTime->setTimestamp($timestamp); + } + + $dtOffset = $dtz->getOffset($dateTime); + if ($dtOffset == 3600 * $offset) { + return $dtz; + } + } + + // No timezone found, fallback to UTC + \OCP\Util::writeLog('datetimezone', 'Failed to find DateTimeZone for offset "' . $offset . "'", ILogger::DEBUG); + return new \DateTimeZone($this->getDefaultTimeZone()); + } + } + + /** + * Get the default timezone of the server + * + * Falls back to UTC if it is not yet set. + * + * @return string + */ + protected function getDefaultTimeZone() { + $serverTimeZone = date_default_timezone_get(); + return $serverTimeZone ?: 'UTC'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Diagnostics/Event.php b/docker/overlays/nextcloud/html/lib/private/Diagnostics/Event.php new file mode 100644 index 0000000..97da22b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Diagnostics/Event.php @@ -0,0 +1,104 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Diagnostics; + +use OCP\Diagnostics\IEvent; + +class Event implements IEvent { + /** + * @var string + */ + protected $id; + + /** + * @var float + */ + protected $start; + + /** + * @var float + */ + protected $end; + + /** + * @var string + */ + protected $description; + + /** + * @param string $id + * @param string $description + * @param float $start + */ + public function __construct($id, $description, $start) { + $this->id = $id; + $this->description = $description; + $this->start = $start; + } + + /** + * @param float $time + */ + public function end($time) { + $this->end = $time; + } + + /** + * @return float + */ + public function getStart() { + return $this->start; + } + + /** + * @return string + */ + public function getId() { + return $this->id; + } + + /** + * @return string + */ + public function getDescription() { + return $this->description; + } + + /** + * @return float + */ + public function getEnd() { + return $this->end; + } + + /** + * @return float + */ + public function getDuration() { + if (!$this->end) { + $this->end = microtime(true); + } + return $this->end - $this->start; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Diagnostics/EventLogger.php b/docker/overlays/nextcloud/html/lib/private/Diagnostics/EventLogger.php new file mode 100644 index 0000000..115a8eb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Diagnostics/EventLogger.php @@ -0,0 +1,83 @@ + + * @author Morris Jobke + * @author Piotr Mrówczyński + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Diagnostics; + +use OCP\Diagnostics\IEventLogger; + +class EventLogger implements IEventLogger { + /** + * @var \OC\Diagnostics\Event[] + */ + private $events = []; + + /** + * @var bool - Module needs to be activated by some app + */ + private $activated = false; + + /** + * @inheritdoc + */ + public function start($id, $description) { + if ($this->activated) { + $this->events[$id] = new Event($id, $description, microtime(true)); + } + } + + /** + * @inheritdoc + */ + public function end($id) { + if ($this->activated && isset($this->events[$id])) { + $timing = $this->events[$id]; + $timing->end(microtime(true)); + } + } + + /** + * @inheritdoc + */ + public function log($id, $description, $start, $end) { + if ($this->activated) { + $this->events[$id] = new Event($id, $description, $start); + $this->events[$id]->end($end); + } + } + + /** + * @inheritdoc + */ + public function getEvents() { + return $this->events; + } + + /** + * @inheritdoc + */ + public function activate() { + $this->activated = true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Diagnostics/Query.php b/docker/overlays/nextcloud/html/lib/private/Diagnostics/Query.php new file mode 100644 index 0000000..a5fb32e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Diagnostics/Query.php @@ -0,0 +1,91 @@ + + * @author Piotr Mrówczyński + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Diagnostics; + +use OCP\Diagnostics\IQuery; + +class Query implements IQuery { + private $sql; + + private $params; + + private $start; + + private $end; + + private $stack; + + /** + * @param string $sql + * @param array $params + * @param int $start + */ + public function __construct($sql, $params, $start, array $stack) { + $this->sql = $sql; + $this->params = $params; + $this->start = $start; + $this->stack = $stack; + } + + public function end($time) { + $this->end = $time; + } + + /** + * @return array + */ + public function getParams() { + return $this->params; + } + + /** + * @return string + */ + public function getSql() { + return $this->sql; + } + + /** + * @return float + */ + public function getStart() { + return $this->start; + } + + /** + * @return float + */ + public function getDuration() { + return $this->end - $this->start; + } + + public function getStartTime() { + return $this->start; + } + + public function getStacktrace() { + return $this->stack; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Diagnostics/QueryLogger.php b/docker/overlays/nextcloud/html/lib/private/Diagnostics/QueryLogger.php new file mode 100644 index 0000000..6093603 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Diagnostics/QueryLogger.php @@ -0,0 +1,96 @@ + + * @author Morris Jobke + * @author Piotr Mrówczyński + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Diagnostics; + +use OC\Cache\CappedMemoryCache; +use OCP\Diagnostics\IQueryLogger; + +class QueryLogger implements IQueryLogger { + /** + * @var \OC\Diagnostics\Query + */ + protected $activeQuery; + + /** + * @var CappedMemoryCache + */ + protected $queries; + + /** + * QueryLogger constructor. + */ + public function __construct() { + $this->queries = new CappedMemoryCache(1024); + } + + + /** + * @var bool - Module needs to be activated by some app + */ + private $activated = false; + + /** + * @inheritdoc + */ + public function startQuery($sql, array $params = null, array $types = null) { + if ($this->activated) { + $this->activeQuery = new Query($sql, $params, microtime(true), $this->getStack()); + } + } + + private function getStack() { + $stack = debug_backtrace(); + array_shift($stack); + array_shift($stack); + array_shift($stack); + return $stack; + } + + /** + * @inheritdoc + */ + public function stopQuery() { + if ($this->activated && $this->activeQuery) { + $this->activeQuery->end(microtime(true)); + $this->queries[] = $this->activeQuery; + $this->activeQuery = null; + } + } + + /** + * @inheritdoc + */ + public function getQueries() { + return $this->queries->getData(); + } + + /** + * @inheritdoc + */ + public function activate() { + $this->activated = true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DirectEditing/Manager.php b/docker/overlays/nextcloud/html/lib/private/DirectEditing/Manager.php new file mode 100644 index 0000000..3542aee --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DirectEditing/Manager.php @@ -0,0 +1,301 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * @author Robin Appelman + * @author Tobias Kaminsky + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DirectEditing; + +use Doctrine\DBAL\FetchMode; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DirectEditing\ACreateFromTemplate; +use OCP\DirectEditing\IEditor; +use \OCP\DirectEditing\IManager; +use OCP\DirectEditing\IToken; +use OCP\Encryption\IManager as EncryptionManager; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\IDBConnection; +use OCP\IL10N; +use OCP\IUserSession; +use OCP\L10N\IFactory; +use OCP\Security\ISecureRandom; +use OCP\Share\IShare; +use Throwable; +use function array_key_exists; +use function in_array; + +class Manager implements IManager { + private const TOKEN_CLEANUP_TIME = 12 * 60 * 60 ; + + public const TABLE_TOKENS = 'direct_edit'; + + /** @var IEditor[] */ + private $editors = []; + /** @var IDBConnection */ + private $connection; + /** @var ISecureRandom */ + private $random; + /** @var string|null */ + private $userId; + /** @var IRootFolder */ + private $rootFolder; + /** @var IL10N */ + private $l10n; + /** @var EncryptionManager */ + private $encryptionManager; + + public function __construct( + ISecureRandom $random, + IDBConnection $connection, + IUserSession $userSession, + IRootFolder $rootFolder, + IFactory $l10nFactory, + EncryptionManager $encryptionManager + ) { + $this->random = $random; + $this->connection = $connection; + $this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null; + $this->rootFolder = $rootFolder; + $this->l10n = $l10nFactory->get('core'); + $this->encryptionManager = $encryptionManager; + } + + public function registerDirectEditor(IEditor $directEditor): void { + $this->editors[$directEditor->getId()] = $directEditor; + } + + public function getEditors(): array { + return $this->editors; + } + + public function getTemplates(string $editor, string $type): array { + if (!array_key_exists($editor, $this->editors)) { + throw new \RuntimeException('No matching editor found'); + } + $templates = []; + foreach ($this->editors[$editor]->getCreators() as $creator) { + if ($creator->getId() === $type) { + $templates = [ + 'empty' => [ + 'id' => 'empty', + 'title' => $this->l10n->t('Empty file'), + 'preview' => null + ] + ]; + + if ($creator instanceof ACreateFromTemplate) { + $templates = $creator->getTemplates(); + } + + $templates = array_map(function ($template) use ($creator) { + $template['extension'] = $creator->getExtension(); + $template['mimetype'] = $creator->getMimetype(); + return $template; + }, $templates); + } + } + $return = []; + $return['templates'] = $templates; + return $return; + } + + public function create(string $path, string $editorId, string $creatorId, $templateId = null): string { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + if ($userFolder->nodeExists($path)) { + throw new \RuntimeException('File already exists'); + } else { + $file = $userFolder->newFile($path); + $editor = $this->getEditor($editorId); + $creators = $editor->getCreators(); + foreach ($creators as $creator) { + if ($creator->getId() === $creatorId) { + $creator->create($file, $creatorId, $templateId); + return $this->createToken($editorId, $file, $path); + } + } + } + + throw new \RuntimeException('No creator found'); + } + + public function open(string $filePath, string $editorId = null): string { + /** @var File $file */ + $file = $this->rootFolder->getUserFolder($this->userId)->get($filePath); + + if ($editorId === null) { + $editorId = $this->findEditorForFile($file); + } + if (!array_key_exists($editorId, $this->editors)) { + throw new \RuntimeException("Editor $editorId is unknown"); + } + + return $this->createToken($editorId, $file, $filePath); + } + + private function findEditorForFile(File $file) { + foreach ($this->editors as $editor) { + if (in_array($file->getMimeType(), $editor->getMimetypes())) { + return $editor->getId(); + } + } + throw new \RuntimeException('No default editor found for files mimetype'); + } + + public function edit(string $token): Response { + try { + /** @var IEditor $editor */ + $tokenObject = $this->getToken($token); + if ($tokenObject->hasBeenAccessed()) { + throw new \RuntimeException('Token has already been used and can only be used for followup requests'); + } + $editor = $this->getEditor($tokenObject->getEditor()); + $this->accessToken($token); + } catch (Throwable $throwable) { + $this->invalidateToken($token); + return new NotFoundResponse(); + } + return $editor->open($tokenObject); + } + + public function editSecure(File $file, string $editorId): TemplateResponse { + // TODO: Implementation in follow up + } + + private function getEditor($editorId): IEditor { + if (!array_key_exists($editorId, $this->editors)) { + throw new \RuntimeException('No editor found'); + } + return $this->editors[$editorId]; + } + + public function getToken(string $token): IToken { + $query = $this->connection->getQueryBuilder(); + $query->select('*')->from(self::TABLE_TOKENS) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + if ($tokenRow = $result->fetch(FetchMode::ASSOCIATIVE)) { + return new Token($this, $tokenRow); + } + throw new \RuntimeException('Failed to validate the token'); + } + + public function cleanup(): int { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TABLE_TOKENS) + ->where($query->expr()->lt('timestamp', $query->createNamedParameter(time() - self::TOKEN_CLEANUP_TIME))); + return $query->execute(); + } + + public function refreshToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->update(self::TABLE_TOKENS) + ->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + + public function invalidateToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TABLE_TOKENS) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + public function accessToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->update(self::TABLE_TOKENS) + ->set('accessed', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)) + ->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + public function invokeTokenScope($userId): void { + \OC_User::setIncognitoMode(true); + \OC_User::setUserId($userId); + } + + public function createToken($editorId, File $file, string $filePath, IShare $share = null): string { + $token = $this->random->generate(64, ISecureRandom::CHAR_HUMAN_READABLE); + $query = $this->connection->getQueryBuilder(); + $query->insert(self::TABLE_TOKENS) + ->values([ + 'token' => $query->createNamedParameter($token), + 'editor_id' => $query->createNamedParameter($editorId), + 'file_id' => $query->createNamedParameter($file->getId()), + 'file_path' => $query->createNamedParameter($filePath), + 'user_id' => $query->createNamedParameter($this->userId), + 'share_id' => $query->createNamedParameter($share !== null ? $share->getId(): null), + 'timestamp' => $query->createNamedParameter(time()) + ]); + $query->execute(); + return $token; + } + + /** + * @param $userId + * @param $fileId + * @param null $filePath + * @return Node + * @throws NotFoundException + */ + public function getFileForToken($userId, $fileId, $filePath = null): Node { + $userFolder = $this->rootFolder->getUserFolder($userId); + if ($filePath !== null) { + return $userFolder->get($filePath); + } + $files = $userFolder->getById($fileId); + if (count($files) === 0) { + throw new NotFoundException('File nound found by id ' . $fileId); + } + return $files[0]; + } + + public function isEnabled(): bool { + if (!$this->encryptionManager->isEnabled()) { + return true; + } + + try { + $moduleId = $this->encryptionManager->getDefaultEncryptionModuleId(); + $module = $this->encryptionManager->getEncryptionModule($moduleId); + /** @var \OCA\Encryption\Util $util */ + $util = \OC::$server->get(\OCA\Encryption\Util::class); + if ($module->isReadyForUser($this->userId) && $util->isMasterKeyEnabled()) { + return true; + } + } catch (Throwable $e) { + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/DirectEditing/Token.php b/docker/overlays/nextcloud/html/lib/private/DirectEditing/Token.php new file mode 100644 index 0000000..eb98f3c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/DirectEditing/Token.php @@ -0,0 +1,74 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\DirectEditing; + +use OCP\DirectEditing\IToken; +use OCP\Files\File; + +class Token implements IToken { + + /** @var Manager */ + private $manager; + private $data; + + public function __construct(Manager $manager, $data) { + $this->manager = $manager; + $this->data = $data; + } + + public function extend(): void { + $this->manager->refreshToken($this->data['token']); + } + + public function invalidate(): void { + $this->manager->invalidateToken($this->data['token']); + } + + public function getFile(): File { + if ($this->data['share_id'] !== null) { + return $this->manager->getShareForToken($this->data['share_id']); + } + return $this->manager->getFileForToken($this->data['user_id'], $this->data['file_id'], $this->data['file_path']); + } + + public function getToken(): string { + return $this->data['token']; + } + + public function useTokenScope(): void { + $this->manager->invokeTokenScope($this->data['user_id']); + } + + public function hasBeenAccessed(): bool { + return (bool) $this->data['accessed']; + } + + public function getEditor(): string { + return $this->data['editor_id']; + } + + public function getUser(): string { + return $this->data['user_id']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/DecryptAll.php b/docker/overlays/nextcloud/html/lib/private/Encryption/DecryptAll.php new file mode 100644 index 0000000..19bd2f8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/DecryptAll.php @@ -0,0 +1,297 @@ + + * @author Björn Schießle + * @author Christian Jürges + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * @author sammo2828 + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\DecryptionFailedException; +use OC\Files\View; +use OCP\Encryption\IEncryptionModule; +use OCP\IUserManager; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class DecryptAll { + + /** @var OutputInterface */ + protected $output; + + /** @var InputInterface */ + protected $input; + + /** @var Manager */ + protected $encryptionManager; + + /** @var IUserManager */ + protected $userManager; + + /** @var View */ + protected $rootView; + + /** @var array files which couldn't be decrypted */ + protected $failed; + + /** + * @param Manager $encryptionManager + * @param IUserManager $userManager + * @param View $rootView + */ + public function __construct( + Manager $encryptionManager, + IUserManager $userManager, + View $rootView + ) { + $this->encryptionManager = $encryptionManager; + $this->userManager = $userManager; + $this->rootView = $rootView; + $this->failed = []; + } + + /** + * start to decrypt all files + * + * @param InputInterface $input + * @param OutputInterface $output + * @param string $user which users data folder should be decrypted, default = all users + * @return bool + * @throws \Exception + */ + public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') { + $this->input = $input; + $this->output = $output; + + if ($user !== '' && $this->userManager->userExists($user) === false) { + $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); + return false; + } + + $this->output->writeln('prepare encryption modules...'); + if ($this->prepareEncryptionModules($user) === false) { + return false; + } + $this->output->writeln(' done.'); + + $this->decryptAllUsersFiles($user); + + if (empty($this->failed)) { + $this->output->writeln('all files could be decrypted successfully!'); + } else { + $this->output->writeln('Files for following users couldn\'t be decrypted, '); + $this->output->writeln('maybe the user is not set up in a way that supports this operation: '); + foreach ($this->failed as $uid => $paths) { + $this->output->writeln(' ' . $uid); + foreach ($paths as $path) { + $this->output->writeln(' ' . $path); + } + } + $this->output->writeln(''); + } + + return true; + } + + /** + * prepare encryption modules to perform the decrypt all function + * + * @param $user + * @return bool + */ + protected function prepareEncryptionModules($user) { + // prepare all encryption modules for decrypt all + $encryptionModules = $this->encryptionManager->getEncryptionModules(); + foreach ($encryptionModules as $moduleDesc) { + /** @var IEncryptionModule $module */ + $module = call_user_func($moduleDesc['callback']); + $this->output->writeln(''); + $this->output->writeln('Prepare "' . $module->getDisplayName() . '"'); + $this->output->writeln(''); + if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) { + $this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!'); + return false; + } + } + + return true; + } + + /** + * iterate over all user and encrypt their files + * + * @param string $user which users files should be decrypted, default = all users + */ + protected function decryptAllUsersFiles($user = '') { + $this->output->writeln("\n"); + + $userList = []; + if ($user === '') { + $fetchUsersProgress = new ProgressBar($this->output); + $fetchUsersProgress->setFormat(" %message% \n [%bar%]"); + $fetchUsersProgress->start(); + $fetchUsersProgress->setMessage("Fetch list of users..."); + $fetchUsersProgress->advance(); + + foreach ($this->userManager->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers('', $limit, $offset); + foreach ($users as $user) { + $userList[] = $user; + } + $offset += $limit; + $fetchUsersProgress->advance(); + } while (count($users) >= $limit); + $fetchUsersProgress->setMessage("Fetch list of users... finished"); + $fetchUsersProgress->finish(); + } + } else { + $userList[] = $user; + } + + $this->output->writeln("\n\n"); + + $progress = new ProgressBar($this->output); + $progress->setFormat(" %message% \n [%bar%]"); + $progress->start(); + $progress->setMessage("starting to decrypt files..."); + $progress->advance(); + + $numberOfUsers = count($userList); + $userNo = 1; + foreach ($userList as $uid) { + $userCount = "$uid ($userNo of $numberOfUsers)"; + $this->decryptUsersFiles($uid, $progress, $userCount); + $userNo++; + } + + $progress->setMessage("starting to decrypt files... finished"); + $progress->finish(); + + $this->output->writeln("\n\n"); + } + + /** + * encrypt files from the given user + * + * @param string $uid + * @param ProgressBar $progress + * @param string $userCount + */ + protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { + $this->setupUserFS($uid); + $directories = []; + $directories[] = '/' . $uid . '/files'; + + while ($root = array_pop($directories)) { + $content = $this->rootView->getDirectoryContent($root); + foreach ($content as $file) { + // only decrypt files owned by the user + if ($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) { + continue; + } + $path = $root . '/' . $file['name']; + if ($this->rootView->is_dir($path)) { + $directories[] = $path; + continue; + } else { + try { + $progress->setMessage("decrypt files for user $userCount: $path"); + $progress->advance(); + if ($file->isEncrypted() === false) { + $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); + $progress->advance(); + } else { + if ($this->decryptFile($path) === false) { + $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); + $progress->advance(); + } + } + } catch (\Exception $e) { + if (isset($this->failed[$uid])) { + $this->failed[$uid][] = $path; + } else { + $this->failed[$uid] = [$path]; + } + } + } + } + } + } + + /** + * encrypt file + * + * @param string $path + * @return bool + */ + protected function decryptFile($path) { + + // skip already decrypted files + $fileInfo = $this->rootView->getFileInfo($path); + if ($fileInfo !== false && !$fileInfo->isEncrypted()) { + return true; + } + + $source = $path; + $target = $path . '.decrypted.' . $this->getTimestamp(); + + try { + $this->rootView->copy($source, $target); + $this->rootView->touch($target, $fileInfo->getMTime()); + $this->rootView->rename($target, $source); + } catch (DecryptionFailedException $e) { + if ($this->rootView->file_exists($target)) { + $this->rootView->unlink($target); + } + return false; + } + + return true; + } + + /** + * get current timestamp + * + * @return int + */ + protected function getTimestamp() { + return time(); + } + + + /** + * setup user file system + * + * @param string $uid + */ + protected function setupUserFS($uid) { + \OC_Util::tearDownFS(); + \OC_Util::setupFS($uid); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/EncryptionWrapper.php b/docker/overlays/nextcloud/html/lib/private/Encryption/EncryptionWrapper.php new file mode 100644 index 0000000..edbdc69 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/EncryptionWrapper.php @@ -0,0 +1,121 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Files\Filesystem; +use OC\Files\Storage\Wrapper\Encryption; +use OC\Files\View; +use OC\Memcache\ArrayCache; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage; +use OCP\ILogger; + +/** + * Class EncryptionWrapper + * + * applies the encryption storage wrapper + * + * @package OC\Encryption + */ +class EncryptionWrapper { + + /** @var ArrayCache */ + private $arrayCache; + + /** @var Manager */ + private $manager; + + /** @var ILogger */ + private $logger; + + /** + * EncryptionWrapper constructor. + * + * @param ArrayCache $arrayCache + * @param Manager $manager + * @param ILogger $logger + */ + public function __construct(ArrayCache $arrayCache, + Manager $manager, + ILogger $logger + ) { + $this->arrayCache = $arrayCache; + $this->manager = $manager; + $this->logger = $logger; + } + + /** + * Wraps the given storage when it is not a shared storage + * + * @param string $mountPoint + * @param Storage $storage + * @param IMountPoint $mount + * @return Encryption|Storage + */ + public function wrapStorage($mountPoint, Storage $storage, IMountPoint $mount) { + $parameters = [ + 'storage' => $storage, + 'mountPoint' => $mountPoint, + 'mount' => $mount + ]; + + if (!$storage->instanceOfStorage(Storage\IDisableEncryptionStorage::class)) { + $user = \OC::$server->getUserSession()->getUser(); + $mountManager = Filesystem::getMountManager(); + $uid = $user ? $user->getUID() : null; + $fileHelper = \OC::$server->getEncryptionFilesHelper(); + $keyStorage = \OC::$server->getEncryptionKeyStorage(); + + $util = new Util( + new View(), + \OC::$server->getUserManager(), + \OC::$server->getGroupManager(), + \OC::$server->getConfig() + ); + $update = new Update( + new View(), + $util, + Filesystem::getMountManager(), + $this->manager, + $fileHelper, + $uid + ); + return new Encryption( + $parameters, + $this->manager, + $util, + $this->logger, + $fileHelper, + $uid, + $keyStorage, + $update, + $mountManager, + $this->arrayCache + ); + } else { + return $storage; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/DecryptionFailedException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/DecryptionFailedException.php new file mode 100644 index 0000000..048732d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/DecryptionFailedException.php @@ -0,0 +1,29 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class DecryptionFailedException extends GenericEncryptionException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php new file mode 100644 index 0000000..a421914 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php @@ -0,0 +1,30 @@ + + * @author Clark Tomlinson + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EmptyEncryptionDataException extends GenericEncryptionException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionFailedException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionFailedException.php new file mode 100644 index 0000000..405e2d5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionFailedException.php @@ -0,0 +1,30 @@ + + * @author Clark Tomlinson + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionFailedException extends GenericEncryptionException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php new file mode 100644 index 0000000..bb55e29 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php @@ -0,0 +1,36 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionHeaderKeyExistsException extends GenericEncryptionException { + + /** + * @param string $key + */ + public function __construct($key) { + parent::__construct('header key "'. $key . '" already reserved by ownCloud'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php new file mode 100644 index 0000000..c44ea9d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php @@ -0,0 +1,32 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionHeaderToLargeException extends GenericEncryptionException { + public function __construct() { + parent::__construct('max header size exceeded'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php new file mode 100644 index 0000000..824dce4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php @@ -0,0 +1,37 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class ModuleAlreadyExistsException extends GenericEncryptionException { + + /** + * @param string $id + * @param string $name + */ + public function __construct($id, $name) { + parent::__construct('Id "' . $id . '" already used by encryption module "' . $name . '"'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php new file mode 100644 index 0000000..24192e6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php @@ -0,0 +1,29 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class ModuleDoesNotExistsException extends GenericEncryptionException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/UnknownCipherException.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/UnknownCipherException.php new file mode 100644 index 0000000..7a8bd76 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Exceptions/UnknownCipherException.php @@ -0,0 +1,29 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class UnknownCipherException extends GenericEncryptionException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/File.php b/docker/overlays/nextcloud/html/lib/private/Encryption/File.php new file mode 100644 index 0000000..8aed19e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/File.php @@ -0,0 +1,128 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Cache\CappedMemoryCache; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Share\IManager; + +class File implements \OCP\Encryption\IFile { + + /** @var Util */ + protected $util; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var IManager */ + private $shareManager; + + /** + * cache results of already checked folders + * + * @var array + */ + protected $cache; + + public function __construct(Util $util, + IRootFolder $rootFolder, + IManager $shareManager) { + $this->util = $util; + $this->cache = new CappedMemoryCache(); + $this->rootFolder = $rootFolder; + $this->shareManager = $shareManager; + } + + + /** + * get list of users with access to the file + * + * @param string $path to the file + * @return array ['users' => $uniqueUserIds, 'public' => $public] + */ + public function getAccessList($path) { + + // Make sure that a share key is generated for the owner too + list($owner, $ownerPath) = $this->util->getUidAndFilename($path); + + // always add owner to the list of users with access to the file + $userIds = [$owner]; + + if (!$this->util->isFile($owner . '/' . $ownerPath)) { + return ['users' => $userIds, 'public' => false]; + } + + $ownerPath = substr($ownerPath, strlen('/files')); + $userFolder = $this->rootFolder->getUserFolder($owner); + try { + $file = $userFolder->get($ownerPath); + } catch (NotFoundException $e) { + $file = null; + } + $ownerPath = $this->util->stripPartialFileExtension($ownerPath); + + // first get the shares for the parent and cache the result so that we don't + // need to check all parents for every file + $parent = dirname($ownerPath); + $parentNode = $userFolder->get($parent); + if (isset($this->cache[$parent])) { + $resultForParents = $this->cache[$parent]; + } else { + $resultForParents = $this->shareManager->getAccessList($parentNode); + $this->cache[$parent] = $resultForParents; + } + $userIds = array_merge($userIds, $resultForParents['users']); + $public = $resultForParents['public'] || $resultForParents['remote']; + + + // Find out who, if anyone, is sharing the file + if ($file !== null) { + $resultForFile = $this->shareManager->getAccessList($file, false); + $userIds = array_merge($userIds, $resultForFile['users']); + $public = $resultForFile['public'] || $resultForFile['remote'] || $public; + } + + // check if it is a group mount + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OCA\Files_External\MountConfig::getSystemMountPoints(); + foreach ($mounts as $mount) { + if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) { + $mountedFor = $this->util->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']); + $userIds = array_merge($userIds, $mountedFor); + } + } + } + + // Remove duplicate UIDs + $uniqueUserIds = array_unique($userIds); + + return ['users' => $uniqueUserIds, 'public' => $public]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/HookManager.php b/docker/overlays/nextcloud/html/lib/private/Encryption/HookManager.php new file mode 100644 index 0000000..8ddd506 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/HookManager.php @@ -0,0 +1,76 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Files\Filesystem; +use OC\Files\View; + +class HookManager { + /** + * @var Update + */ + private static $updater; + + public static function postShared($params) { + self::getUpdate()->postShared($params); + } + public static function postUnshared($params) { + self::getUpdate()->postUnshared($params); + } + + public static function postRename($params) { + self::getUpdate()->postRename($params); + } + + public static function postRestore($params) { + self::getUpdate()->postRestore($params); + } + + /** + * @return Update + */ + private static function getUpdate() { + if (is_null(self::$updater)) { + $user = \OC::$server->getUserSession()->getUser(); + $uid = ''; + if ($user) { + $uid = $user->getUID(); + } + self::$updater = new Update( + new View(), + new Util( + new View(), + \OC::$server->getUserManager(), + \OC::$server->getGroupManager(), + \OC::$server->getConfig()), + Filesystem::getMountManager(), + \OC::$server->getEncryptionManager(), + \OC::$server->getEncryptionFilesHelper(), + $uid + ); + } + + return self::$updater; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Keys/Storage.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Keys/Storage.php new file mode 100644 index 0000000..43a291b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Keys/Storage.php @@ -0,0 +1,490 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\Filesystem; +use OC\Files\View; +use OC\ServerNotAvailableException; +use OC\User\NoUserException; +use OCP\Encryption\Keys\IStorage; +use OCP\IConfig; +use OCP\Security\ICrypto; + +class Storage implements IStorage { + + // hidden file which indicate that the folder is a valid key storage + public const KEY_STORAGE_MARKER = '.oc_key_storage'; + + /** @var View */ + private $view; + + /** @var Util */ + private $util; + + // base dir where all the file related keys are stored + /** @var string */ + private $keys_base_dir; + + // root of the key storage default is empty which means that we use the data folder + /** @var string */ + private $root_dir; + + /** @var string */ + private $encryption_base_dir; + + /** @var string */ + private $backup_base_dir; + + /** @var array */ + private $keyCache = []; + + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + + /** + * @param View $view + * @param Util $util + */ + public function __construct(View $view, Util $util, ICrypto $crypto, IConfig $config) { + $this->view = $view; + $this->util = $util; + + $this->encryption_base_dir = '/files_encryption'; + $this->keys_base_dir = $this->encryption_base_dir .'/keys'; + $this->backup_base_dir = $this->encryption_base_dir .'/backup'; + $this->root_dir = $this->util->getKeyStorageRoot(); + $this->crypto = $crypto; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getUserKey($uid, $keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return base64_decode($this->getKeyWithUid($path, $uid)); + } + + /** + * @inheritdoc + */ + public function getFileKey($path, $keyId, $encryptionModuleId) { + $realFile = $this->util->stripPartialFileExtension($path); + $keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile); + $key = $this->getKey($keyDir . $keyId)['key']; + + if ($key === '' && $realFile !== $path) { + // Check if the part file has keys and use them, if no normal keys + // exist. This is required to fix copyBetweenStorage() when we + // rename a .part file over storage borders. + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + $key = $this->getKey($keyDir . $keyId)['key']; + } + + return base64_decode($key); + } + + /** + * @inheritdoc + */ + public function getSystemUserKey($keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return base64_decode($this->getKeyWithUid($path, null)); + } + + /** + * @inheritdoc + */ + public function setUserKey($uid, $keyId, $key, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return $this->setKey($path, [ + 'key' => base64_encode($key), + 'uid' => $uid, + ]); + } + + /** + * @inheritdoc + */ + public function setFileKey($path, $keyId, $key, $encryptionModuleId) { + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + return $this->setKey($keyDir . $keyId, [ + 'key' => base64_encode($key), + ]); + } + + /** + * @inheritdoc + */ + public function setSystemUserKey($keyId, $key, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return $this->setKey($path, [ + 'key' => base64_encode($key), + 'uid' => null, + ]); + } + + /** + * @inheritdoc + */ + public function deleteUserKey($uid, $keyId, $encryptionModuleId) { + try { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return !$this->view->file_exists($path) || $this->view->unlink($path); + } catch (NoUserException $e) { + // this exception can come from initMountPoints() from setupUserMounts() + // for a deleted user. + // + // It means, that: + // - we are not running in alternative storage mode because we don't call + // initMountPoints() in that mode + // - the keys were in the user's home but since the user was deleted, the + // user's home is gone and so are the keys + // + // So there is nothing to do, just ignore. + } + } + + /** + * @inheritdoc + */ + public function deleteFileKey($path, $keyId, $encryptionModuleId) { + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + return !$this->view->file_exists($keyDir . $keyId) || $this->view->unlink($keyDir . $keyId); + } + + /** + * @inheritdoc + */ + public function deleteAllFileKeys($path) { + $keyDir = $this->getFileKeyDir('', $path); + return !$this->view->file_exists($keyDir) || $this->view->deleteAll($keyDir); + } + + /** + * @inheritdoc + */ + public function deleteSystemUserKey($keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return !$this->view->file_exists($path) || $this->view->unlink($path); + } + + /** + * construct path to users key + * + * @param string $encryptionModuleId + * @param string $keyId + * @param string $uid + * @return string + */ + protected function constructUserKeyPath($encryptionModuleId, $keyId, $uid) { + if ($uid === null) { + $path = $this->root_dir . '/' . $this->encryption_base_dir . '/' . $encryptionModuleId . '/' . $keyId; + } else { + $path = $this->root_dir . '/' . $uid . $this->encryption_base_dir . '/' + . $encryptionModuleId . '/' . $uid . '.' . $keyId; + } + + return \OC\Files\Filesystem::normalizePath($path); + } + + /** + * @param string $path + * @param string|null $uid + * @return string + * @throws ServerNotAvailableException + * + * Small helper function to fetch the key and verify the value for user and system keys + */ + private function getKeyWithUid(string $path, ?string $uid): string { + $data = $this->getKey($path); + + if (!isset($data['key'])) { + throw new ServerNotAvailableException('Key is invalid'); + } + + if ($data['key'] === '') { + return ''; + } + + if (!array_key_exists('uid', $data) || $data['uid'] !== $uid) { + // If the migration is done we error out + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + return $data['key']; + } + + if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) { + throw new ServerNotAvailableException('Key has been modified'); + } else { + //Otherwise we migrate + $data['uid'] = $uid; + $this->setKey($path, $data); + } + } + + return $data['key']; + } + + /** + * read key from hard disk + * + * @param string $path to key + * @return array containing key as base64encoded key, and possible the uid + */ + private function getKey($path): array { + $key = [ + 'key' => '', + ]; + + if ($this->view->file_exists($path)) { + if (isset($this->keyCache[$path])) { + $key = $this->keyCache[$path]; + } else { + $data = $this->view->file_get_contents($path); + + // Version <20.0.0.1 doesn't have this + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + $key = [ + 'key' => base64_encode($data), + ]; + } else { + if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) { + try { + $clearData = $this->crypto->decrypt($data); + } catch (\Exception $e) { + throw new ServerNotAvailableException('Could not decrypt key', 0, $e); + } + + $dataArray = json_decode($clearData, true); + if ($dataArray === null) { + throw new ServerNotAvailableException('Invalid encryption key'); + } + + $key = $dataArray; + } else { + /* + * Even if not all keys are migrated we should still try to decrypt it (in case some have moved). + * However it is only a failure now if it is an array and decryption fails + */ + $fallback = false; + try { + $clearData = $this->crypto->decrypt($data); + } catch (\Exception $e) { + $fallback = true; + } + + if (!$fallback) { + $dataArray = json_decode($clearData, true); + if ($dataArray === null) { + throw new ServerNotAvailableException('Invalid encryption key'); + } + $key = $dataArray; + } else { + $key = [ + 'key' => base64_encode($data), + ]; + } + } + } + + $this->keyCache[$path] = $key; + } + } + + return $key; + } + + /** + * write key to disk + * + * + * @param string $path path to key directory + * @param array $key key + * @return bool + */ + private function setKey($path, $key) { + $this->keySetPreparation(dirname($path)); + + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + // Only store old format if this happens during the migration. + // TODO: Remove for 21 + $data = base64_decode($key['key']); + } else { + // Wrap the data + $data = $this->crypto->encrypt(json_encode($key)); + } + + $result = $this->view->file_put_contents($path, $data); + + if (is_int($result) && $result > 0) { + $this->keyCache[$path] = $key; + return true; + } + + return false; + } + + /** + * get path to key folder for a given file + * + * @param string $encryptionModuleId + * @param string $path path to the file, relative to data/ + * @return string + */ + private function getFileKeyDir($encryptionModuleId, $path) { + list($owner, $filename) = $this->util->getUidAndFilename($path); + + // in case of system wide mount points the keys are stored directly in the data directory + if ($this->util->isSystemWideMountPoint($filename, $owner)) { + $keyPath = $this->root_dir . '/' . $this->keys_base_dir . $filename . '/'; + } else { + $keyPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $filename . '/'; + } + + return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false); + } + + /** + * move keys if a file was renamed + * + * @param string $source + * @param string $target + * @return boolean + */ + public function renameKeys($source, $target) { + $sourcePath = $this->getPathToKeys($source); + $targetPath = $this->getPathToKeys($target); + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->rename($sourcePath, $targetPath); + + return true; + } + + return false; + } + + + /** + * copy keys if a file was renamed + * + * @param string $source + * @param string $target + * @return boolean + */ + public function copyKeys($source, $target) { + $sourcePath = $this->getPathToKeys($source); + $targetPath = $this->getPathToKeys($target); + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->copy($sourcePath, $targetPath); + return true; + } + + return false; + } + + /** + * backup keys of a given encryption module + * + * @param string $encryptionModuleId + * @param string $purpose + * @param string $uid + * @return bool + * @since 12.0.0 + */ + public function backupUserKeys($encryptionModuleId, $purpose, $uid) { + $source = $uid . $this->encryption_base_dir . '/' . $encryptionModuleId; + $backupDir = $uid . $this->backup_base_dir; + if (!$this->view->file_exists($backupDir)) { + $this->view->mkdir($backupDir); + } + + $backupDir = $backupDir . '/' . $purpose . '.' . $encryptionModuleId . '.' . $this->getTimestamp(); + $this->view->mkdir($backupDir); + + return $this->view->copy($source, $backupDir); + } + + /** + * get the current timestamp + * + * @return int + */ + protected function getTimestamp() { + return time(); + } + + /** + * get system wide path and detect mount points + * + * @param string $path + * @return string + */ + protected function getPathToKeys($path) { + list($owner, $relativePath) = $this->util->getUidAndFilename($path); + $systemWideMountPoint = $this->util->isSystemWideMountPoint($relativePath, $owner); + + if ($systemWideMountPoint) { + $systemPath = $this->root_dir . '/' . $this->keys_base_dir . $relativePath . '/'; + } else { + $systemPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $relativePath . '/'; + } + + return Filesystem::normalizePath($systemPath, false); + } + + /** + * Make preparations to filesystem for saving a key file + * + * @param string $path relative to the views root + */ + protected function keySetPreparation($path) { + // If the file resides within a subdirectory, create it + if (!$this->view->file_exists($path)) { + $sub_dirs = explode('/', ltrim($path, '/')); + $dir = ''; + foreach ($sub_dirs as $sub_dir) { + $dir .= '/' . $sub_dir; + if (!$this->view->is_dir($dir)) { + $this->view->mkdir($dir); + } + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Manager.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Manager.php new file mode 100644 index 0000000..f160a16 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Manager.php @@ -0,0 +1,273 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Encryption\Keys\Storage; +use OC\Files\Filesystem; +use OC\Files\View; +use OC\Memcache\ArrayCache; +use OC\ServiceUnavailableException; +use OCP\Encryption\IEncryptionModule; +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; + +class Manager implements IManager { + + /** @var array */ + protected $encryptionModules; + + /** @var IConfig */ + protected $config; + + /** @var ILogger */ + protected $logger; + + /** @var Il10n */ + protected $l; + + /** @var View */ + protected $rootView; + + /** @var Util */ + protected $util; + + /** @var ArrayCache */ + protected $arrayCache; + + /** + * @param IConfig $config + * @param ILogger $logger + * @param IL10N $l10n + * @param View $rootView + * @param Util $util + * @param ArrayCache $arrayCache + */ + public function __construct(IConfig $config, ILogger $logger, IL10N $l10n, View $rootView, Util $util, ArrayCache $arrayCache) { + $this->encryptionModules = []; + $this->config = $config; + $this->logger = $logger; + $this->l = $l10n; + $this->rootView = $rootView; + $this->util = $util; + $this->arrayCache = $arrayCache; + } + + /** + * Check if encryption is enabled + * + * @return bool true if enabled, false if not + */ + public function isEnabled() { + $installed = $this->config->getSystemValue('installed', false); + if (!$installed) { + return false; + } + + $enabled = $this->config->getAppValue('core', 'encryption_enabled', 'no'); + return $enabled === 'yes'; + } + + /** + * check if new encryption is ready + * + * @return bool + * @throws ServiceUnavailableException + */ + public function isReady() { + if ($this->isKeyStorageReady() === false) { + throw new ServiceUnavailableException('Key Storage is not ready'); + } + + return true; + } + + /** + * @param string $user + */ + public function isReadyForUser($user) { + if (!$this->isReady()) { + return false; + } + + foreach ($this->getEncryptionModules() as $module) { + /** @var IEncryptionModule $m */ + $m = call_user_func($module['callback']); + if (!$m->isReadyForUser($user)) { + return false; + } + } + + return true; + } + + /** + * Registers an callback function which must return an encryption module instance + * + * @param string $id + * @param string $displayName + * @param callable $callback + * @throws Exceptions\ModuleAlreadyExistsException + */ + public function registerEncryptionModule($id, $displayName, callable $callback) { + if (isset($this->encryptionModules[$id])) { + throw new Exceptions\ModuleAlreadyExistsException($id, $displayName); + } + + $this->encryptionModules[$id] = [ + 'id' => $id, + 'displayName' => $displayName, + 'callback' => $callback, + ]; + + $defaultEncryptionModuleId = $this->getDefaultEncryptionModuleId(); + + if (empty($defaultEncryptionModuleId)) { + $this->setDefaultEncryptionModule($id); + } + } + + /** + * Unregisters an encryption module + * + * @param string $moduleId + */ + public function unregisterEncryptionModule($moduleId) { + unset($this->encryptionModules[$moduleId]); + } + + /** + * get a list of all encryption modules + * + * @return array [id => ['id' => $id, 'displayName' => $displayName, 'callback' => callback]] + */ + public function getEncryptionModules() { + return $this->encryptionModules; + } + + /** + * get a specific encryption module + * + * @param string $moduleId + * @return IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getEncryptionModule($moduleId = '') { + if (!empty($moduleId)) { + if (isset($this->encryptionModules[$moduleId])) { + return call_user_func($this->encryptionModules[$moduleId]['callback']); + } else { + $message = "Module with ID: $moduleId does not exist."; + $hint = $this->l->t('Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.', [$moduleId]); + throw new Exceptions\ModuleDoesNotExistsException($message, $hint); + } + } else { + return $this->getDefaultEncryptionModule(); + } + } + + /** + * get default encryption module + * + * @return \OCP\Encryption\IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + protected function getDefaultEncryptionModule() { + $defaultModuleId = $this->getDefaultEncryptionModuleId(); + if (!empty($defaultModuleId)) { + if (isset($this->encryptionModules[$defaultModuleId])) { + return call_user_func($this->encryptionModules[$defaultModuleId]['callback']); + } else { + $message = 'Default encryption module not loaded'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } else { + $message = 'No default encryption module defined'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } + + /** + * set default encryption module Id + * + * @param string $moduleId + * @return bool + */ + public function setDefaultEncryptionModule($moduleId) { + try { + $this->getEncryptionModule($moduleId); + } catch (\Exception $e) { + return false; + } + + $this->config->setAppValue('core', 'default_encryption_module', $moduleId); + return true; + } + + /** + * get default encryption module Id + * + * @return string + */ + public function getDefaultEncryptionModuleId() { + return $this->config->getAppValue('core', 'default_encryption_module'); + } + + /** + * Add storage wrapper + */ + public function setupStorage() { + // If encryption is disabled and there are no loaded modules it makes no sense to load the wrapper + if (!empty($this->encryptionModules) || $this->isEnabled()) { + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); + Filesystem::addStorageWrapper('oc_encryption', [$encryptionWrapper, 'wrapStorage'], 2); + } + } + + + /** + * check if key storage is ready + * + * @return bool + */ + protected function isKeyStorageReady() { + $rootDir = $this->util->getKeyStorageRoot(); + + // the default root is always valid + if ($rootDir === '') { + return true; + } + + // check if key storage is mounted correctly + if ($this->rootView->file_exists($rootDir . '/' . Storage::KEY_STORAGE_MARKER)) { + return true; + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Update.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Update.php new file mode 100644 index 0000000..beb76a2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Update.php @@ -0,0 +1,193 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Files\Filesystem; +use OC\Files\Mount; +use OC\Files\View; + +/** + * update encrypted files, e.g. because a file was shared + */ +class Update { + + /** @var \OC\Files\View */ + protected $view; + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OC\Files\Mount\Manager */ + protected $mountManager; + + /** @var \OC\Encryption\Manager */ + protected $encryptionManager; + + /** @var string */ + protected $uid; + + /** @var \OC\Encryption\File */ + protected $file; + + /** + * + * @param \OC\Files\View $view + * @param \OC\Encryption\Util $util + * @param \OC\Files\Mount\Manager $mountManager + * @param \OC\Encryption\Manager $encryptionManager + * @param \OC\Encryption\File $file + * @param string $uid + */ + public function __construct( + View $view, + Util $util, + Mount\Manager $mountManager, + Manager $encryptionManager, + File $file, + $uid + ) { + $this->view = $view; + $this->util = $util; + $this->mountManager = $mountManager; + $this->encryptionManager = $encryptionManager; + $this->file = $file; + $this->uid = $uid; + } + + /** + * hook after file was shared + * + * @param array $params + */ + public function postShared($params) { + if ($this->encryptionManager->isEnabled()) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $path = Filesystem::getPath($params['fileSource']); + list($owner, $ownerPath) = $this->getOwnerPath($path); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + } + + /** + * hook after file was unshared + * + * @param array $params + */ + public function postUnshared($params) { + if ($this->encryptionManager->isEnabled()) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $path = Filesystem::getPath($params['fileSource']); + list($owner, $ownerPath) = $this->getOwnerPath($path); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + } + + /** + * inform encryption module that a file was restored from the trash bin, + * e.g. to update the encryption keys + * + * @param array $params + */ + public function postRestore($params) { + if ($this->encryptionManager->isEnabled()) { + $path = Filesystem::normalizePath('/' . $this->uid . '/files/' . $params['filePath']); + $this->update($path); + } + } + + /** + * inform encryption module that a file was renamed, + * e.g. to update the encryption keys + * + * @param array $params + */ + public function postRename($params) { + $source = $params['oldpath']; + $target = $params['newpath']; + if ( + $this->encryptionManager->isEnabled() && + dirname($source) !== dirname($target) + ) { + list($owner, $ownerPath) = $this->getOwnerPath($target); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + + /** + * get owner and path relative to data//files + * + * @param string $path path to file for current user + * @return array ['owner' => $owner, 'path' => $path] + * @throw \InvalidArgumentException + */ + protected function getOwnerPath($path) { + $info = Filesystem::getFileInfo($path); + $owner = Filesystem::getOwner($path); + $view = new View('/' . $owner . '/files'); + $path = $view->getPath($info->getId()); + if ($path === null) { + throw new \InvalidArgumentException('No file found for ' . $info->getId()); + } + + return [$owner, $path]; + } + + /** + * notify encryption module about added/removed users from a file/folder + * + * @param string $path relative to data/ + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function update($path) { + $encryptionModule = $this->encryptionManager->getEncryptionModule(); + + // if the encryption module doesn't encrypt the files on a per-user basis + // we have nothing to do here. + if ($encryptionModule->needDetailedAccessList() === false) { + return; + } + + // if a folder was shared, get a list of all (sub-)folders + if ($this->view->is_dir($path)) { + $allFiles = $this->util->getAllFiles($path); + } else { + $allFiles = [$path]; + } + + + + foreach ($allFiles as $file) { + $usersSharing = $this->file->getAccessList($file); + $encryptionModule->update($file, $this->uid, $usersSharing); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Encryption/Util.php b/docker/overlays/nextcloud/html/lib/private/Encryption/Util.php new file mode 100644 index 0000000..0bda00a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Encryption/Util.php @@ -0,0 +1,403 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Jan-Christoph Borchardt + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; +use OC\Encryption\Exceptions\EncryptionHeaderToLargeException; +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\Encryption\IEncryptionModule; +use OCP\IConfig; +use OCP\IUser; + +class Util { + public const HEADER_START = 'HBEGIN'; + public const HEADER_END = 'HEND'; + public const HEADER_PADDING_CHAR = '-'; + + public const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module'; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $headerSize = 8192; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $blockSize = 8192; + + /** @var View */ + protected $rootView; + + /** @var array */ + protected $ocHeaderKeys; + + /** @var \OC\User\Manager */ + protected $userManager; + + /** @var IConfig */ + protected $config; + + /** @var array paths excluded from encryption */ + protected $excludedPaths; + + /** @var \OC\Group\Manager $manager */ + protected $groupManager; + + /** + * + * @param View $rootView + * @param \OC\User\Manager $userManager + * @param \OC\Group\Manager $groupManager + * @param IConfig $config + */ + public function __construct( + View $rootView, + \OC\User\Manager $userManager, + \OC\Group\Manager $groupManager, + IConfig $config) { + $this->ocHeaderKeys = [ + self::HEADER_ENCRYPTION_MODULE_KEY + ]; + + $this->rootView = $rootView; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->config = $config; + + $this->excludedPaths[] = 'files_encryption'; + $this->excludedPaths[] = 'appdata_' . $config->getSystemValue('instanceid', null); + $this->excludedPaths[] = 'files_external'; + } + + /** + * read encryption module ID from header + * + * @param array $header + * @return string + * @throws ModuleDoesNotExistsException + */ + public function getEncryptionModuleId(array $header = null) { + $id = ''; + $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY; + + if (isset($header[$encryptionModuleKey])) { + $id = $header[$encryptionModuleKey]; + } elseif (isset($header['cipher'])) { + if (class_exists('\OCA\Encryption\Crypto\Encryption')) { + // fall back to default encryption if the user migrated from + // ownCloud <= 8.0 with the old encryption + $id = \OCA\Encryption\Crypto\Encryption::ID; + } else { + throw new ModuleDoesNotExistsException('Default encryption module missing'); + } + } + + return $id; + } + + /** + * create header for encrypted file + * + * @param array $headerData + * @param IEncryptionModule $encryptionModule + * @return string + * @throws EncryptionHeaderToLargeException if header has to many arguments + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + public function createHeader(array $headerData, IEncryptionModule $encryptionModule) { + $header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':'; + foreach ($headerData as $key => $value) { + if (in_array($key, $this->ocHeaderKeys)) { + throw new EncryptionHeaderKeyExistsException($key); + } + $header .= $key . ':' . $value . ':'; + } + $header .= self::HEADER_END; + + if (strlen($header) > $this->getHeaderSize()) { + throw new EncryptionHeaderToLargeException(); + } + + $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT); + + return $paddedHeader; + } + + /** + * go recursively through a dir and collect all files and sub files. + * + * @param string $dir relative to the users files folder + * @return array with list of files relative to the users files folder + */ + public function getAllFiles($dir) { + $result = []; + $dirList = [$dir]; + + while ($dirList) { + $dir = array_pop($dirList); + $content = $this->rootView->getDirectoryContent($dir); + + foreach ($content as $c) { + if ($c->getType() === 'dir') { + $dirList[] = $c->getPath(); + } else { + $result[] = $c->getPath(); + } + } + } + + return $result; + } + + /** + * check if it is a file uploaded by the user stored in data/user/files + * or a metadata file + * + * @param string $path relative to the data/ folder + * @return boolean + */ + public function isFile($path) { + $parts = explode('/', Filesystem::normalizePath($path), 4); + if (isset($parts[2]) && $parts[2] === 'files') { + return true; + } + return false; + } + + /** + * return size of encryption header + * + * @return integer + */ + public function getHeaderSize() { + return $this->headerSize; + } + + /** + * return size of block read by a PHP stream + * + * @return integer + */ + public function getBlockSize() { + return $this->blockSize; + } + + /** + * get the owner and the path for the file relative to the owners files folder + * + * @param string $path + * @return array + * @throws \BadMethodCallException + */ + public function getUidAndFilename($path) { + $parts = explode('/', $path); + $uid = ''; + if (count($parts) > 2) { + $uid = $parts[1]; + } + if (!$this->userManager->userExists($uid)) { + throw new \BadMethodCallException( + 'path needs to be relative to the system wide data folder and point to a user specific file' + ); + } + + $ownerPath = implode('/', array_slice($parts, 2)); + + return [$uid, Filesystem::normalizePath($ownerPath)]; + } + + /** + * Remove .path extension from a file path + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + public function stripPartialFileExtension($path) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + if ($extension === 'part') { + $newLength = strlen($path) - 5; // 5 = strlen(".part") + $fPath = substr($path, 0, $newLength); + + // if path also contains a transaction id, we remove it too + $extension = pathinfo($fPath, PATHINFO_EXTENSION); + if (substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId") + $newLength = strlen($fPath) - strlen($extension) -1; + $fPath = substr($fPath, 0, $newLength); + } + return $fPath; + } else { + return $path; + } + } + + public function getUserWithAccessToMountPoint($users, $groups) { + $result = []; + if (in_array('all', $users)) { + $users = $this->userManager->search('', null, null); + $result = array_map(function (IUser $user) { + return $user->getUID(); + }, $users); + } else { + $result = array_merge($result, $users); + + $groupManager = \OC::$server->getGroupManager(); + foreach ($groups as $group) { + $groupObject = $groupManager->get($group); + if ($groupObject) { + $foundUsers = $groupObject->searchUsers('', -1, 0); + $userIds = []; + foreach ($foundUsers as $user) { + $userIds[] = $user->getUID(); + } + $result = array_merge($result, $userIds); + } + } + } + + return $result; + } + + /** + * check if the file is stored on a system wide mount point + * @param string $path relative to /data/user with leading '/' + * @param string $uid + * @return boolean + */ + public function isSystemWideMountPoint($path, $uid) { + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OCA\Files_External\MountConfig::getSystemMountPoints(); + foreach ($mounts as $mount) { + if (strpos($path, '/files/' . $mount['mountpoint']) === 0) { + if ($this->isMountPointApplicableToUser($mount, $uid)) { + return true; + } + } + } + } + return false; + } + + /** + * check if mount point is applicable to user + * + * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups'] + * @param string $uid + * @return boolean + */ + private function isMountPointApplicableToUser($mount, $uid) { + $acceptedUids = ['all', $uid]; + // check if mount point is applicable for the user + $intersection = array_intersect($acceptedUids, $mount['applicable']['users']); + if (!empty($intersection)) { + return true; + } + // check if mount point is applicable for group where the user is a member + foreach ($mount['applicable']['groups'] as $gid) { + if ($this->groupManager->isInGroup($uid, $gid)) { + return true; + } + } + return false; + } + + /** + * check if it is a path which is excluded by ownCloud from encryption + * + * @param string $path + * @return boolean + */ + public function isExcluded($path) { + $normalizedPath = Filesystem::normalizePath($path); + $root = explode('/', $normalizedPath, 4); + if (count($root) > 1) { + + // detect alternative key storage root + $rootDir = $this->getKeyStorageRoot(); + if ($rootDir !== '' && + 0 === strpos( + Filesystem::normalizePath($path), + Filesystem::normalizePath($rootDir) + ) + ) { + return true; + } + + + //detect system wide folders + if (in_array($root[1], $this->excludedPaths)) { + return true; + } + + // detect user specific folders + if ($this->userManager->userExists($root[1]) + && in_array($root[2], $this->excludedPaths)) { + return true; + } + } + return false; + } + + /** + * check if recovery key is enabled for user + * + * @param string $uid + * @return boolean + */ + public function recoveryEnabled($uid) { + $enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0'); + + return $enabled === '1'; + } + + /** + * set new key storage root + * + * @param string $root new key store root relative to the data folder + */ + public function setKeyStorageRoot($root) { + $this->config->setAppValue('core', 'encryption_key_storage_root', $root); + } + + /** + * get key storage root + * + * @return string key storage root + */ + public function getKeyStorageRoot() { + return $this->config->getAppValue('core', 'encryption_key_storage_root', ''); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/EventDispatcher/EventDispatcher.php b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/EventDispatcher.php new file mode 100644 index 0000000..f2a87cc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/EventDispatcher.php @@ -0,0 +1,108 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\EventDispatcher; + +use function get_class; +use OC\Broadcast\Events\BroadcastEvent; +use OCP\Broadcast\Events\IBroadcastEvent; +use OCP\EventDispatcher\ABroadcastedEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IContainer; +use OCP\ILogger; +use OCP\IServerContainer; +use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher; + +class EventDispatcher implements IEventDispatcher { + + /** @var SymfonyDispatcher */ + private $dispatcher; + + /** @var IContainer */ + private $container; + + /** @var ILogger */ + private $logger; + + public function __construct(SymfonyDispatcher $dispatcher, + IServerContainer $container, + ILogger $logger) { + $this->dispatcher = $dispatcher; + $this->container = $container; + $this->logger = $logger; + } + + public function addListener(string $eventName, + callable $listener, + int $priority = 0): void { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + public function removeListener(string $eventName, + callable $listener): void { + $this->dispatcher->removeListener($eventName, $listener); + } + + public function addServiceListener(string $eventName, + string $className, + int $priority = 0): void { + $listener = new ServiceEventListener( + $this->container, + $className, + $this->logger + ); + + $this->addListener($eventName, $listener, $priority); + } + + public function dispatch(string $eventName, + Event $event): void { + $this->dispatcher->dispatch($event, $eventName); + + if ($event instanceof ABroadcastedEvent && !$event->isPropagationStopped()) { + // Propagate broadcast + $this->dispatch( + IBroadcastEvent::class, + new BroadcastEvent($event) + ); + } + } + + public function dispatchTyped(Event $event): void { + $this->dispatch(get_class($event), $event); + } + + /** + * @return SymfonyDispatcher + * @deprecated 20.0.0 + */ + public function getSymfonyDispatcher(): SymfonyDispatcher { + return $this->dispatcher; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/EventDispatcher/GenericEventWrapper.php b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/GenericEventWrapper.php new file mode 100644 index 0000000..916297f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/GenericEventWrapper.php @@ -0,0 +1,115 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\EventDispatcher; + +use OCP\ILogger; +use Symfony\Component\EventDispatcher\GenericEvent; + +class GenericEventWrapper extends GenericEvent { + + /** @var ILogger */ + private $logger; + + /** @var GenericEvent */ + private $event; + + /** @var string */ + private $eventName; + + public function __construct(ILogger $logger, string $eventName, ?GenericEvent $event) { + parent::__construct($eventName); + $this->logger = $logger; + $this->event = $event; + $this->eventName = $eventName; + } + + private function log() { + $class = ($this->event !== null && is_object($this->event)) ? get_class($this->event) : 'null'; + $this->logger->info( + 'Deprecated event type for {name}: {class} is used', + [ 'name' => $this->eventName, 'class' => $class] + ); + } + + public function isPropagationStopped(): bool { + $this->log(); + return $this->event->isPropagationStopped(); + } + + public function stopPropagation(): void { + $this->log(); + $this->event->stopPropagation(); + } + + public function getSubject() { + $this->log(); + return $this->event->getSubject(); + } + + public function getArgument($key) { + $this->log(); + return $this->event->getArgument($key); + } + + public function setArgument($key, $value) { + $this->log(); + return $this->event->setArgument($key, $value); + } + + public function getArguments() { + return $this->event->getArguments(); + } + + public function setArguments(array $args = []) { + return $this->event->setArguments($args); + } + + public function hasArgument($key) { + return $this->event->hasArgument($key); + } + + public function offsetGet($key) { + return $this->event->offsetGet($key); + } + + public function offsetSet($key, $value) { + return $this->event->offsetSet($key, $value); + } + + public function offsetUnset($key) { + return $this->event->offsetUnset($key); + } + + public function offsetExists($key) { + return $this->event->offsetExists($key); + } + + public function getIterator() { + return$this->event->getIterator(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/EventDispatcher/ServiceEventListener.php b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/ServiceEventListener.php new file mode 100644 index 0000000..a648884 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/ServiceEventListener.php @@ -0,0 +1,78 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\EventDispatcher; + +use OCP\AppFramework\QueryException; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\IContainer; +use OCP\ILogger; + +/** + * Lazy service event listener + * + * Makes it possible to lazy-route a dispatched event to a service instance + * created by the service container + */ +final class ServiceEventListener { + + /** @var IContainer */ + private $container; + + /** @var string */ + private $class; + + /** @var ILogger */ + private $logger; + + /** @var null|IEventListener */ + private $service; + + public function __construct(IContainer $container, + string $class, + ILogger $logger) { + $this->container = $container; + $this->class = $class; + $this->logger = $logger; + } + + public function __invoke(Event $event) { + if ($this->service === null) { + try { + $this->service = $this->container->query($this->class); + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'level' => ILogger::ERROR, + 'message' => "Could not load event listener service " . $this->class, + ]); + return; + } + } + + $this->service->handle($event); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/EventDispatcher/SymfonyAdapter.php b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/SymfonyAdapter.php new file mode 100644 index 0000000..9389e72 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/EventDispatcher/SymfonyAdapter.php @@ -0,0 +1,174 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\EventDispatcher; + +use Symfony\Component\EventDispatcher\GenericEvent; +use function is_callable; +use OCP\EventDispatcher\Event; +use OCP\ILogger; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @deprecated 20.0.0 use \OCP\EventDispatcher\IEventDispatcher + */ +class SymfonyAdapter implements EventDispatcherInterface { + + /** @var EventDispatcher */ + private $eventDispatcher; + /** @var ILogger */ + private $logger; + + /** + * @deprecated 20.0.0 + */ + public function __construct(EventDispatcher $eventDispatcher, ILogger $logger) { + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; + } + + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event|null $event The event to pass to the event handlers/listeners + * If not supplied, an empty Event instance is created + * + * @return void + * @deprecated 20.0.0 + */ + public function dispatch($eventName, $event = null) { + // type hinting is not possible, due to usage of GenericEvent + if ($event instanceof Event) { + $this->eventDispatcher->dispatch($eventName, $event); + } else { + if ($event instanceof GenericEvent && get_class($event) === GenericEvent::class) { + $newEvent = new GenericEventWrapper($this->logger, $eventName, $event); + } else { + $newEvent = $event; + + // Legacy event + $this->logger->info( + 'Deprecated event type for {name}: {class}', + ['name' => $eventName, 'class' => is_object($event) ? get_class($event) : 'null'] + ); + } + $this->eventDispatcher->getSymfonyDispatcher()->dispatch($eventName, $newEvent); + } + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + * @deprecated 20.0.0 + */ + public function addListener($eventName, $listener, $priority = 0) { + if (is_callable($listener)) { + $this->eventDispatcher->addListener($eventName, $listener, $priority); + } else { + // Legacy listener + $this->eventDispatcher->getSymfonyDispatcher()->addListener($eventName, $listener, $priority); + } + } + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + * @deprecated 20.0.0 + */ + public function addSubscriber(EventSubscriberInterface $subscriber) { + $this->eventDispatcher->getSymfonyDispatcher()->addSubscriber($subscriber); + } + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + * @deprecated 20.0.0 + */ + public function removeListener($eventName, $listener) { + $this->eventDispatcher->getSymfonyDispatcher()->removeListener($eventName, $listener); + } + + /** + * @deprecated 20.0.0 + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) { + $this->eventDispatcher->getSymfonyDispatcher()->removeSubscriber($subscriber); + } + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string|null $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + * @deprecated 20.0.0 + */ + public function getListeners($eventName = null) { + return $this->eventDispatcher->getSymfonyDispatcher()->getListeners($eventName); + } + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + * @deprecated 20.0.0 + */ + public function getListenerPriority($eventName, $listener) { + return $this->eventDispatcher->getSymfonyDispatcher()->getListenerPriority($eventName, $listener); + } + + /** + * Checks whether an event has any registered listeners. + * + * @param string|null $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + * @deprecated 20.0.0 + */ + public function hasListeners($eventName = null) { + return $this->eventDispatcher->getSymfonyDispatcher()->hasListeners($eventName); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationFactory.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationFactory.php new file mode 100644 index 0000000..f1bae28 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationFactory.php @@ -0,0 +1,65 @@ + + * + * @author Bjoern Schiessle + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OCP\Federation\ICloudFederationFactory; +use OCP\Federation\ICloudFederationNotification; +use OCP\Federation\ICloudFederationShare; + +class CloudFederationFactory implements ICloudFederationFactory { + + /** + * get a CloudFederationShare Object to prepare a share you want to send + * + * @param string $shareWith + * @param string $name resource name (e.g. document.odt) + * @param string $description share description (optional) + * @param string $providerId resource UID on the provider side + * @param string $owner provider specific UID of the user who owns the resource + * @param string $ownerDisplayName display name of the user who shared the item + * @param string $sharedBy provider specific UID of the user who shared the resource + * @param string $sharedByDisplayName display name of the user who shared the resource + * @param string $sharedSecret used to authenticate requests across servers + * @param string $shareType ('group' or 'user' share) + * @param $resourceType ('file', 'calendar',...) + * @return ICloudFederationShare + * + * @since 14.0.0 + */ + public function getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $sharedSecret, $shareType, $resourceType) { + return new CloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $shareType, $resourceType, $sharedSecret); + } + + /** + * get a Cloud FederationNotification object to prepare a notification you + * want to send + * + * @return ICloudFederationNotification + * + * @since 14.0.0 + */ + public function getCloudFederationNotification() { + return new CloudFederationNotification(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationNotification.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationNotification.php new file mode 100644 index 0000000..ad1d689 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationNotification.php @@ -0,0 +1,67 @@ + + * + * @author Bjoern Schiessle + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OCP\Federation\ICloudFederationNotification; + +/** + * Class CloudFederationNotification + * + * @package OC\Federation + * + * @since 14.0.0 + */ +class CloudFederationNotification implements ICloudFederationNotification { + private $message = []; + + /** + * add a message to the notification + * + * @param string $notificationType (e.g. SHARE_ACCEPTED) + * @param string $resourceType (e.g. file, calendar, contact,...) + * @param string $providerId id of the share + * @param array $notification payload of the notification + * + * @since 14.0.0 + */ + public function setMessage($notificationType, $resourceType, $providerId, array $notification) { + $this->message = [ + 'notificationType' => $notificationType, + 'resourceType' => $resourceType, + 'providerId' => $providerId, + 'notification' => $notification, + ]; + } + + /** + * get message, ready to send out + * + * @return array + * + * @since 14.0.0 + */ + public function getMessage() { + return $this->message; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationProviderManager.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationProviderManager.php new file mode 100644 index 0000000..459d82c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationProviderManager.php @@ -0,0 +1,239 @@ + + * + * @author Bjoern Schiessle + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OC\AppFramework\Http; +use OCP\App\IAppManager; +use OCP\Federation\Exceptions\ProviderDoesNotExistsException; +use OCP\Federation\ICloudFederationNotification; +use OCP\Federation\ICloudFederationProvider; +use OCP\Federation\ICloudFederationProviderManager; +use OCP\Federation\ICloudFederationShare; +use OCP\Federation\ICloudIdManager; +use OCP\Http\Client\IClientService; +use OCP\ILogger; + +/** + * Class Manager + * + * Manage Cloud Federation Providers + * + * @package OC\Federation + */ +class CloudFederationProviderManager implements ICloudFederationProviderManager { + + /** @var array list of available cloud federation providers */ + private $cloudFederationProvider; + + /** @var IAppManager */ + private $appManager; + + /** @var IClientService */ + private $httpClientService; + + /** @var ICloudIdManager */ + private $cloudIdManager; + + /** @var ILogger */ + private $logger; + + /** @var array cache OCM end-points */ + private $ocmEndPoints = []; + + private $supportedAPIVersion = '1.0-proposal1'; + + /** + * CloudFederationProviderManager constructor. + * + * @param IAppManager $appManager + * @param IClientService $httpClientService + * @param ICloudIdManager $cloudIdManager + * @param ILogger $logger + */ + public function __construct(IAppManager $appManager, + IClientService $httpClientService, + ICloudIdManager $cloudIdManager, + ILogger $logger) { + $this->cloudFederationProvider= []; + $this->appManager = $appManager; + $this->httpClientService = $httpClientService; + $this->cloudIdManager = $cloudIdManager; + $this->logger = $logger; + } + + + /** + * Registers an callback function which must return an cloud federation provider + * + * @param string $resourceType which resource type does the provider handles + * @param string $displayName user facing name of the federated share provider + * @param callable $callback + */ + public function addCloudFederationProvider($resourceType, $displayName, callable $callback) { + $this->cloudFederationProvider[$resourceType] = [ + 'resourceType' => $resourceType, + 'displayName' => $displayName, + 'callback' => $callback, + ]; + } + + /** + * remove cloud federation provider + * + * @param string $providerId + */ + public function removeCloudFederationProvider($providerId) { + unset($this->cloudFederationProvider[$providerId]); + } + + /** + * get a list of all cloudFederationProviders + * + * @return array [resourceType => ['resourceType' => $resourceType, 'displayName' => $displayName, 'callback' => callback]] + */ + public function getAllCloudFederationProviders() { + return $this->cloudFederationProvider; + } + + /** + * get a specific cloud federation provider + * + * @param string $resourceType + * @return ICloudFederationProvider + * @throws ProviderDoesNotExistsException + */ + public function getCloudFederationProvider($resourceType) { + if (isset($this->cloudFederationProvider[$resourceType])) { + return call_user_func($this->cloudFederationProvider[$resourceType]['callback']); + } else { + throw new ProviderDoesNotExistsException($resourceType); + } + } + + public function sendShare(ICloudFederationShare $share) { + $cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith()); + $ocmEndPoint = $this->getOCMEndPoint($cloudID->getRemote()); + if (empty($ocmEndPoint)) { + return false; + } + + $client = $this->httpClientService->newClient(); + try { + $response = $client->post($ocmEndPoint . '/shares', [ + 'body' => json_encode($share->getShare()), + 'headers' => ['content-type' => 'application/json'], + 'timeout' => 10, + 'connect_timeout' => 10, + ]); + + if ($response->getStatusCode() === Http::STATUS_CREATED) { + $result = json_decode($response->getBody(), true); + return (is_array($result)) ? $result : []; + } + } catch (\Exception $e) { + // if flat re-sharing is not supported by the remote server + // we re-throw the exception and fall back to the old behaviour. + // (flat re-shares has been introduced in Nextcloud 9.1) + if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) { + $this->logger->debug($e->getMessage()); + throw $e; + } + } + + return false; + } + + /** + * @param string $url + * @param ICloudFederationNotification $notification + * @return mixed + */ + public function sendNotification($url, ICloudFederationNotification $notification) { + $ocmEndPoint = $this->getOCMEndPoint($url); + + if (empty($ocmEndPoint)) { + return false; + } + + $client = $this->httpClientService->newClient(); + try { + $response = $client->post($ocmEndPoint . '/notifications', [ + 'body' => json_encode($notification->getMessage()), + 'headers' => ['content-type' => 'application/json'], + 'timeout' => 10, + 'connect_timeout' => 10, + ]); + if ($response->getStatusCode() === Http::STATUS_CREATED) { + $result = json_decode($response->getBody(), true); + return (is_array($result)) ? $result : []; + } + } catch (\Exception $e) { + // log the error and return false + $this->logger->error('error while sending notification for federated share: ' . $e->getMessage()); + } + + return false; + } + + /** + * check if the new cloud federation API is ready to be used + * + * @return bool + */ + public function isReady() { + return $this->appManager->isEnabledForUser('cloud_federation_api'); + } + /** + * check if server supports the new OCM api and ask for the correct end-point + * + * @param string $url full base URL of the cloud server + * @return string + */ + protected function getOCMEndPoint($url) { + if (isset($this->ocmEndPoints[$url])) { + return $this->ocmEndPoints[$url]; + } + + $client = $this->httpClientService->newClient(); + try { + $response = $client->get($url . '/ocm-provider/', ['timeout' => 10, 'connect_timeout' => 10]); + } catch (\Exception $e) { + $this->ocmEndPoints[$url] = ''; + return ''; + } + + $result = $response->getBody(); + $result = json_decode($result, true); + + $supportedVersion = isset($result['apiVersion']) && $result['apiVersion'] === $this->supportedAPIVersion; + + if (isset($result['endPoint']) && $supportedVersion) { + $this->ocmEndPoints[$url] = $result['endPoint']; + return $result['endPoint']; + } + + $this->ocmEndPoints[$url] = ''; + return ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationShare.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationShare.php new file mode 100644 index 0000000..52acee5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudFederationShare.php @@ -0,0 +1,358 @@ + + * + * @author Bjoern Schiessle + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OCP\Federation\ICloudFederationShare; +use OCP\Share\IShare; + +class CloudFederationShare implements ICloudFederationShare { + private $share = [ + 'shareWith' => '', + 'shareType' => '', + 'name' => '', + 'resourceType' => '', + 'description' => '', + 'providerId' => '', + 'owner' => '', + 'ownerDisplayName' => '', + 'sharedBy' => '', + 'sharedByDisplayName' => '', + 'protocol' => [] + ]; + + /** + * get a CloudFederationShare Object to prepare a share you want to send + * + * @param string $shareWith + * @param string $name resource name (e.g. document.odt) + * @param string $description share description (optional) + * @param string $providerId resource UID on the provider side + * @param string $owner provider specific UID of the user who owns the resource + * @param string $ownerDisplayName display name of the user who shared the item + * @param string $sharedBy provider specific UID of the user who shared the resource + * @param string $sharedByDisplayName display name of the user who shared the resource + * @param string $shareType ('group' or 'user' share) + * @param string $resourceType ('file', 'calendar',...) + * @param string $sharedSecret + */ + public function __construct($shareWith = '', + $name = '', + $description = '', + $providerId = '', + $owner = '', + $ownerDisplayName = '', + $sharedBy = '', + $sharedByDisplayName = '', + $shareType = '', + $resourceType = '', + $sharedSecret = '' + ) { + $this->setShareWith($shareWith); + $this->setResourceName($name); + $this->setDescription($description); + $this->setProviderId($providerId); + $this->setOwner($owner); + $this->setOwnerDisplayName($ownerDisplayName); + $this->setSharedBy($sharedBy); + $this->setSharedByDisplayName($sharedByDisplayName); + $this->setProtocol([ + 'name' => 'webdav', + 'options' => [ + 'sharedSecret' => $sharedSecret, + 'permissions' => '{http://open-cloud-mesh.org/ns}share-permissions' + ] + ]); + $this->setShareType($shareType); + $this->setResourceType($resourceType); + } + + /** + * set uid of the recipient + * + * @param string $user + * + * @since 14.0.0 + */ + public function setShareWith($user) { + $this->share['shareWith'] = $user; + } + + /** + * set resource name (e.g. document.odt) + * + * @param string $name + * + * @since 14.0.0 + */ + public function setResourceName($name) { + $this->share['name'] = $name; + } + + /** + * set resource type (e.g. file, calendar, contact,...) + * + * @param string $resourceType + * + * @since 14.0.0 + */ + public function setResourceType($resourceType) { + $this->share['resourceType'] = $resourceType; + } + + /** + * set resource description (optional) + * + * @param string $description + * + * @since 14.0.0 + */ + public function setDescription($description) { + $this->share['description'] = $description; + } + + /** + * set provider ID (e.g. file ID) + * + * @param string $providerId + * + * @since 14.0.0 + */ + public function setProviderId($providerId) { + $this->share['providerId'] = $providerId; + } + + /** + * set owner UID + * + * @param string $owner + * + * @since 14.0.0 + */ + public function setOwner($owner) { + $this->share['owner'] = $owner; + } + + /** + * set owner display name + * + * @param string $ownerDisplayName + * + * @since 14.0.0 + */ + public function setOwnerDisplayName($ownerDisplayName) { + $this->share['ownerDisplayName'] = $ownerDisplayName; + } + + /** + * set UID of the user who sends the share + * + * @param string $sharedBy + * + * @since 14.0.0 + */ + public function setSharedBy($sharedBy) { + $this->share['sharedBy'] = $sharedBy; + } + + /** + * set display name of the user who sends the share + * + * @param $sharedByDisplayName + * + * @since 14.0.0 + */ + public function setSharedByDisplayName($sharedByDisplayName) { + $this->share['sharedByDisplayName'] = $sharedByDisplayName; + } + + /** + * set protocol specification + * + * @param array $protocol + * + * @since 14.0.0 + */ + public function setProtocol(array $protocol) { + $this->share['protocol'] = $protocol; + } + + /** + * share type (group or user) + * + * @param string $shareType + * + * @since 14.0.0 + */ + public function setShareType($shareType) { + if ($shareType === 'group' || $shareType === IShare::TYPE_REMOTE_GROUP) { + $this->share['shareType'] = 'group'; + } else { + $this->share['shareType'] = 'user'; + } + } + + /** + * get the whole share, ready to send out + * + * @return array + * + * @since 14.0.0 + */ + public function getShare() { + return $this->share; + } + + /** + * get uid of the recipient + * + * @return string + * + * @since 14.0.0 + */ + public function getShareWith() { + return $this->share['shareWith']; + } + + /** + * get resource name (e.g. file, calendar, contact,...) + * + * @return string + * + * @since 14.0.0 + */ + public function getResourceName() { + return $this->share['name']; + } + + /** + * get resource type (e.g. file, calendar, contact,...) + * + * @return string + * + * @since 14.0.0 + */ + public function getResourceType() { + return $this->share['resourceType']; + } + + /** + * get resource description (optional) + * + * @return string + * + * @since 14.0.0 + */ + public function getDescription() { + return $this->share['description']; + } + + /** + * get provider ID (e.g. file ID) + * + * @return string + * + * @since 14.0.0 + */ + public function getProviderId() { + return $this->share['providerId']; + } + + /** + * get owner UID + * + * @return string + * + * @since 14.0.0 + */ + public function getOwner() { + return $this->share['owner']; + } + + /** + * get owner display name + * + * @return string + * + * @since 14.0.0 + */ + public function getOwnerDisplayName() { + return $this->share['ownerDisplayName']; + } + + /** + * get UID of the user who sends the share + * + * @return string + * + * @since 14.0.0 + */ + public function getSharedBy() { + return $this->share['sharedBy']; + } + + /** + * get display name of the user who sends the share + * + * @return string + * + * @since 14.0.0 + */ + public function getSharedByDisplayName() { + return $this->share['sharedByDisplayName']; + } + + /** + * get share type (group or user) + * + * @return string + * + * @since 14.0.0 + */ + public function getShareType() { + return $this->share['shareType']; + } + + /** + * get share Secret + * + * @return string + * + * @since 14.0.0 + */ + public function getShareSecret() { + return $this->share['protocol']['options']['sharedSecret']; + } + + /** + * get protocol specification + * + * @return array + * + * @since 14.0.0 + */ + public function getProtocol() { + return $this->share['protocol']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudId.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudId.php new file mode 100644 index 0000000..80e1745 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudId.php @@ -0,0 +1,83 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OCP\Federation\ICloudId; + +class CloudId implements ICloudId { + /** @var string */ + private $id; + /** @var string */ + private $user; + /** @var string */ + private $remote; + + /** + * CloudId constructor. + * + * @param string $id + * @param string $user + * @param string $remote + */ + public function __construct(string $id, string $user, string $remote) { + $this->id = $id; + $this->user = $user; + $this->remote = $remote; + } + + /** + * The full remote cloud id + * + * @return string + */ + public function getId(): string { + return $this->id; + } + + public function getDisplayId(): string { + return str_replace('https://', '', str_replace('http://', '', $this->getId())); + } + + /** + * The username on the remote server + * + * @return string + */ + public function getUser(): string { + return $this->user; + } + + /** + * The base address of the remote server + * + * @return string + */ + public function getRemote(): string { + return $this->remote; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Federation/CloudIdManager.php b/docker/overlays/nextcloud/html/lib/private/Federation/CloudIdManager.php new file mode 100644 index 0000000..a5ebc98 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Federation/CloudIdManager.php @@ -0,0 +1,119 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Federation; + +use OCP\Federation\ICloudId; +use OCP\Federation\ICloudIdManager; + +class CloudIdManager implements ICloudIdManager { + /** + * @param string $cloudId + * @return ICloudId + * @throws \InvalidArgumentException + */ + public function resolveCloudId(string $cloudId): ICloudId { + // TODO magic here to get the url and user instead of just splitting on @ + + if (!$this->isValidCloudId($cloudId)) { + throw new \InvalidArgumentException('Invalid cloud id'); + } + + // Find the first character that is not allowed in user names + $id = $this->fixRemoteURL($cloudId); + $posSlash = strpos($id, '/'); + $posColon = strpos($id, ':'); + + if ($posSlash === false && $posColon === false) { + $invalidPos = \strlen($id); + } elseif ($posSlash === false) { + $invalidPos = $posColon; + } elseif ($posColon === false) { + $invalidPos = $posSlash; + } else { + $invalidPos = min($posSlash, $posColon); + } + + // Find the last @ before $invalidPos + $pos = $lastAtPos = 0; + while ($lastAtPos !== false && $lastAtPos <= $invalidPos) { + $pos = $lastAtPos; + $lastAtPos = strpos($id, '@', $pos + 1); + } + + if ($pos !== false) { + $user = substr($id, 0, $pos); + $remote = substr($id, $pos + 1); + if (!empty($user) && !empty($remote)) { + return new CloudId($id, $user, $remote); + } + } + throw new \InvalidArgumentException('Invalid cloud id'); + } + + /** + * @param string $user + * @param string $remote + * @return CloudId + */ + public function getCloudId(string $user, string $remote): ICloudId { + // TODO check what the correct url is for remote (asking the remote) + return new CloudId($user. '@' . $remote, $user, $remote); + } + + /** + * Strips away a potential file names and trailing slashes: + * - http://localhost + * - http://localhost/ + * - http://localhost/index.php + * - http://localhost/index.php/s/{shareToken} + * + * all return: http://localhost + * + * @param string $remote + * @return string + */ + protected function fixRemoteURL(string $remote): string { + $remote = str_replace('\\', '/', $remote); + if ($fileNamePosition = strpos($remote, '/index.php')) { + $remote = substr($remote, 0, $fileNamePosition); + } + $remote = rtrim($remote, '/'); + + return $remote; + } + + /** + * @param string $cloudId + * @return bool + */ + public function isValidCloudId(string $cloudId): bool { + return strpos($cloudId, '@') !== false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/AppData/AppData.php b/docker/overlays/nextcloud/html/lib/private/Files/AppData/AppData.php new file mode 100644 index 0000000..5f917af --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/AppData/AppData.php @@ -0,0 +1,182 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\AppData; + +use OC\Cache\CappedMemoryCache; +use OC\Files\SimpleFS\SimpleFolder; +use OC\SystemConfig; +use OCP\Files\Folder; +use OCP\Files\IAppData; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFolder; + +class AppData implements IAppData { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var SystemConfig */ + private $config; + + /** @var string */ + private $appId; + + /** @var Folder */ + private $folder; + + /** @var (ISimpleFolder|NotFoundException)[]|CappedMemoryCache */ + private $folders; + + /** + * AppData constructor. + * + * @param IRootFolder $rootFolder + * @param SystemConfig $systemConfig + * @param string $appId + */ + public function __construct(IRootFolder $rootFolder, + SystemConfig $systemConfig, + string $appId) { + $this->rootFolder = $rootFolder; + $this->config = $systemConfig; + $this->appId = $appId; + $this->folders = new CappedMemoryCache(); + } + + private function getAppDataFolderName() { + $instanceId = $this->config->getValue('instanceid', null); + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + + return 'appdata_' . $instanceId; + } + + private function getAppDataRootFolder(): Folder { + $name = $this->getAppDataFolderName(); + + try { + /** @var Folder $node */ + $node = $this->rootFolder->get($name); + return $node; + } catch (NotFoundException $e) { + try { + return $this->rootFolder->newFolder($name); + } catch (NotPermittedException $e) { + throw new \RuntimeException('Could not get appdata folder'); + } + } + } + + /** + * @return Folder + * @throws \RuntimeException + */ + private function getAppDataFolder(): Folder { + if ($this->folder === null) { + $name = $this->getAppDataFolderName(); + + try { + $this->folder = $this->rootFolder->get($name . '/' . $this->appId); + } catch (NotFoundException $e) { + $appDataRootFolder = $this->getAppDataRootFolder(); + + try { + $this->folder = $appDataRootFolder->get($this->appId); + } catch (NotFoundException $e) { + try { + $this->folder = $appDataRootFolder->newFolder($this->appId); + } catch (NotPermittedException $e) { + throw new \RuntimeException('Could not get appdata folder for ' . $this->appId); + } + } + } + } + + return $this->folder; + } + + public function getFolder(string $name): ISimpleFolder { + $key = $this->appId . '/' . $name; + if ($cachedFolder = $this->folders->get($key)) { + if ($cachedFolder instanceof \Exception) { + throw $cachedFolder; + } else { + return $cachedFolder; + } + } + try { + // Hardening if somebody wants to retrieve '/' + if ($name === '/') { + $node = $this->getAppDataFolder(); + } else { + $path = $this->getAppDataFolderName() . '/' . $this->appId . '/' . $name; + $node = $this->rootFolder->get($path); + } + } catch (NotFoundException $e) { + $this->folders->set($key, $e); + throw $e; + } + + /** @var Folder $node */ + $folder = new SimpleFolder($node); + $this->folders->set($key, $folder); + return $folder; + } + + public function newFolder(string $name): ISimpleFolder { + $key = $this->appId . '/' . $name; + $folder = $this->getAppDataFolder()->newFolder($name); + + $simpleFolder = new SimpleFolder($folder); + $this->folders->set($key, $simpleFolder); + return $simpleFolder; + } + + public function getDirectoryListing(): array { + $listing = $this->getAppDataFolder()->getDirectoryListing(); + + $fileListing = array_map(function (Node $folder) { + if ($folder instanceof Folder) { + return new SimpleFolder($folder); + } + return null; + }, $listing); + + $fileListing = array_filter($fileListing); + + return array_values($fileListing); + } + + public function getId(): int { + return $this->getAppDataFolder()->getId(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/AppData/Factory.php b/docker/overlays/nextcloud/html/lib/private/Files/AppData/Factory.php new file mode 100644 index 0000000..4801b24 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/AppData/Factory.php @@ -0,0 +1,59 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\AppData; + +use OC\SystemConfig; +use OCP\Files\IRootFolder; + +class Factory { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var SystemConfig */ + private $config; + + private $folders = []; + + public function __construct(IRootFolder $rootFolder, + SystemConfig $systemConfig) { + $this->rootFolder = $rootFolder; + $this->config = $systemConfig; + } + + /** + * @param string $appId + * @return AppData + */ + public function get(string $appId): AppData { + if (!isset($this->folders[$appId])) { + $this->folders[$appId] = new AppData($this->rootFolder, $this->config, $appId); + } + return $this->folders[$appId]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/AbstractCacheEvent.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/AbstractCacheEvent.php new file mode 100644 index 0000000..a402947 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/AbstractCacheEvent.php @@ -0,0 +1,83 @@ + + * + * @author Joas Schilling + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +use OCP\EventDispatcher\Event; +use OCP\Files\Cache\ICacheEvent; +use OCP\Files\Storage\IStorage; + +class AbstractCacheEvent extends Event implements ICacheEvent { + protected $storage; + protected $path; + protected $fileId; + + /** + * @param IStorage $storage + * @param string $path + * @param int $fileId + * @since 16.0.0 + */ + public function __construct(IStorage $storage, string $path, int $fileId) { + $this->storage = $storage; + $this->path = $path; + $this->fileId = $fileId; + } + + /** + * @return IStorage + * @since 16.0.0 + */ + public function getStorage(): IStorage { + return $this->storage; + } + + /** + * @return string + * @since 16.0.0 + */ + public function getPath(): string { + return $this->path; + } + + /** + * @param string $path + * @since 19.0.0 + */ + public function setPath(string $path): void { + $this->path = $path; + } + + /** + * @return int + * @since 16.0.0 + */ + public function getFileId(): int { + return $this->fileId; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Cache.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Cache.php new file mode 100644 index 0000000..8a173f3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Cache.php @@ -0,0 +1,1002 @@ + + * @author Ari Selseng + * @author Artem Kochnev + * @author Björn Schießle + * @author Christoph Wurst + * @author Florin Peter + * @author Frédéric Fortier + * @author Jens-Christian Fischer + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Cache\CacheInsertEvent; +use OCP\Files\Cache\CacheUpdateEvent; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileInfo; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\Search\ISearchQuery; +use OCP\Files\Storage\IStorage; +use OCP\IDBConnection; + +/** + * Metadata cache for a storage + * + * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: + * + * - Scanner: scans the storage and updates the cache where needed + * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected + * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed + * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater + */ +class Cache implements ICache { + use MoveFromCacheTrait { + MoveFromCacheTrait::moveFromCache as moveFromCacheFallback; + } + + /** + * @var array partial data for the cache + */ + protected $partial = []; + + /** + * @var string + */ + protected $storageId; + + private $storage; + + /** + * @var Storage $storageCache + */ + protected $storageCache; + + /** @var IMimeTypeLoader */ + protected $mimetypeLoader; + + /** + * @var IDBConnection + */ + protected $connection; + + protected $eventDispatcher; + + /** @var QuerySearchHelper */ + protected $querySearchHelper; + + /** + * @param IStorage $storage + */ + public function __construct(IStorage $storage) { + $this->storageId = $storage->getId(); + $this->storage = $storage; + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + + $this->storageCache = new Storage($storage); + $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); + $this->connection = \OC::$server->getDatabaseConnection(); + $this->eventDispatcher = \OC::$server->getEventDispatcher(); + $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader); + } + + private function getQueryBuilder() { + return new CacheQueryBuilder( + $this->connection, + \OC::$server->getSystemConfig(), + \OC::$server->getLogger(), + $this + ); + } + + /** + * Get the numeric storage id for this cache's storage + * + * @return int + */ + public function getNumericStorageId() { + return $this->storageCache->getNumericId(); + } + + /** + * get the stored metadata of a file or folder + * + * @param string | int $file either the path of a file or folder or the file id for a file or folder + * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache + */ + public function get($file) { + $query = $this->getQueryBuilder(); + $query->selectFileCache(); + + if (is_string($file) or $file == '') { + // normalize file + $file = $this->normalize($file); + + $query->whereStorageId() + ->wherePath($file); + } else { //file id + $query->whereFileId($file); + } + + $data = $query->execute()->fetch(); + + //merge partial data + if (!$data and is_string($file) and isset($this->partial[$file])) { + return $this->partial[$file]; + } elseif (!$data) { + return $data; + } else { + return self::cacheEntryFromData($data, $this->mimetypeLoader); + } + } + + /** + * Create a CacheEntry from database row + * + * @param array $data + * @param IMimeTypeLoader $mimetypeLoader + * @return CacheEntry + */ + public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) { + //fix types + $data['fileid'] = (int)$data['fileid']; + $data['parent'] = (int)$data['parent']; + $data['size'] = 0 + $data['size']; + $data['mtime'] = (int)$data['mtime']; + $data['storage_mtime'] = (int)$data['storage_mtime']; + $data['encryptedVersion'] = (int)$data['encrypted']; + $data['encrypted'] = (bool)$data['encrypted']; + $data['storage_id'] = $data['storage']; + $data['storage'] = (int)$data['storage']; + $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']); + $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']); + if ($data['storage_mtime'] == 0) { + $data['storage_mtime'] = $data['mtime']; + } + $data['permissions'] = (int)$data['permissions']; + if (isset($data['creation_time'])) { + $data['creation_time'] = (int) $data['creation_time']; + } + if (isset($data['upload_time'])) { + $data['upload_time'] = (int) $data['upload_time']; + } + return new CacheEntry($data); + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + */ + public function getFolderContents($folder) { + $fileId = $this->getId($folder); + return $this->getFolderContentsById($fileId); + } + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return ICacheEntry[] + */ + public function getFolderContentsById($fileId) { + if ($fileId > -1) { + $query = $this->getQueryBuilder(); + $query->selectFileCache() + ->whereParent($fileId) + ->orderBy('name', 'ASC'); + + $files = $query->execute()->fetchAll(); + return array_map(function (array $data) { + return self::cacheEntryFromData($data, $this->mimetypeLoader); + }, $files); + } + return []; + } + + /** + * insert or update meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + return $this->insert($file, $data); + } + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + // normalize file + $file = $this->normalize($file); + + if (isset($this->partial[$file])) { //add any saved partial data + $data = array_merge($this->partial[$file], $data); + unset($this->partial[$file]); + } + + $requiredFields = ['size', 'mtime', 'mimetype']; + foreach ($requiredFields as $field) { + if (!isset($data[$field])) { //data not complete save as partial and return + $this->partial[$file] = $data; + return -1; + } + } + + $data['path'] = $file; + if (!isset($data['parent'])) { + $data['parent'] = $this->getParentId($file); + } + $data['name'] = basename($file); + + [$values, $extensionValues] = $this->normalizeData($data); + $values['storage'] = $this->getNumericStorageId(); + + try { + $builder = $this->connection->getQueryBuilder(); + $builder->insert('filecache'); + + foreach ($values as $column => $value) { + $builder->setValue($column, $builder->createNamedParameter($value)); + } + + if ($builder->execute()) { + $fileId = $builder->getLastInsertId(); + + if (count($extensionValues)) { + $query = $this->getQueryBuilder(); + $query->insert('filecache_extended'); + + $query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); + foreach ($extensionValues as $column => $value) { + $query->setValue($column, $query->createNamedParameter($value)); + } + $query->execute(); + } + + $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId)); + return $fileId; + } + } catch (UniqueConstraintViolationException $e) { + // entry exists already + if ($this->connection->inTransaction()) { + $this->connection->commit(); + $this->connection->beginTransaction(); + } + } + + // The file was created in the mean time + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.'); + } + } + + /** + * update the metadata of an existing file or folder in the cache + * + * @param int $id the fileid of the existing file or folder + * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged + */ + public function update($id, array $data) { + if (isset($data['path'])) { + // normalize path + $data['path'] = $this->normalize($data['path']); + } + + if (isset($data['name'])) { + // normalize path + $data['name'] = $this->normalize($data['name']); + } + + [$values, $extensionValues] = $this->normalizeData($data); + + if (count($values)) { + $query = $this->getQueryBuilder(); + + $query->update('filecache') + ->whereFileId($id) + ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) { + return $query->expr()->orX( + $query->expr()->neq($key, $query->createNamedParameter($value)), + $query->expr()->isNull($key) + ); + }, array_keys($values), array_values($values)))); + + foreach ($values as $key => $value) { + $query->set($key, $query->createNamedParameter($value)); + } + + $query->execute(); + } + + if (count($extensionValues)) { + try { + $query = $this->getQueryBuilder(); + $query->insert('filecache_extended'); + + $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)); + foreach ($extensionValues as $column => $value) { + $query->setValue($column, $query->createNamedParameter($value)); + } + + $query->execute(); + } catch (UniqueConstraintViolationException $e) { + $query = $this->getQueryBuilder(); + $query->update('filecache_extended') + ->whereFileId($id) + ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) { + return $query->expr()->orX( + $query->expr()->neq($key, $query->createNamedParameter($value)), + $query->expr()->isNull($key) + ); + }, array_keys($extensionValues), array_values($extensionValues)))); + + foreach ($extensionValues as $key => $value) { + $query->set($key, $query->createNamedParameter($value)); + } + + $query->execute(); + } + } + + $path = $this->getPathById($id); + // path can still be null if the file doesn't exist + if ($path !== null) { + $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id)); + } + } + + /** + * extract query parts and params array from data array + * + * @param array $data + * @return array + */ + protected function normalizeData(array $data): array { + $fields = [ + 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', + 'etag', 'permissions', 'checksum', 'storage']; + $extensionFields = ['metadata_etag', 'creation_time', 'upload_time']; + + $doNotCopyStorageMTime = false; + if (array_key_exists('mtime', $data) && $data['mtime'] === null) { + // this horrific magic tells it to not copy storage_mtime to mtime + unset($data['mtime']); + $doNotCopyStorageMTime = true; + } + + $params = []; + $extensionParams = []; + foreach ($data as $name => $value) { + if (array_search($name, $fields) !== false) { + if ($name === 'path') { + $params['path_hash'] = md5($value); + } elseif ($name === 'mimetype') { + $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/'))); + $value = $this->mimetypeLoader->getId($value); + } elseif ($name === 'storage_mtime') { + if (!$doNotCopyStorageMTime && !isset($data['mtime'])) { + $params['mtime'] = $value; + } + } elseif ($name === 'encrypted') { + if (isset($data['encryptedVersion'])) { + $value = $data['encryptedVersion']; + } else { + // Boolean to integer conversion + $value = $value ? 1 : 0; + } + } + $params[$name] = $value; + } + if (array_search($name, $extensionFields) !== false) { + $extensionParams[$name] = $value; + } + } + return [$params, array_filter($extensionParams)]; + } + + /** + * get the file id for a file + * + * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file + * + * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing + * + * @param string $file + * @return int + */ + public function getId($file) { + // normalize file + $file = $this->normalize($file); + + $query = $this->getQueryBuilder(); + $query->select('fileid') + ->from('filecache') + ->whereStorageId() + ->wherePath($file); + + $id = $query->execute()->fetchColumn(); + return $id === false ? -1 : (int)$id; + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + if ($file === '') { + return -1; + } else { + $parent = $this->getParentPath($file); + return (int)$this->getId($parent); + } + } + + private function getParentPath($path) { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + return $parent; + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getId($file) != -1; + } + + /** + * remove a file or folder from the cache + * + * when removing a folder from the cache all files and folders inside the folder will be removed as well + * + * @param string $file + */ + public function remove($file) { + $entry = $this->get($file); + + if ($entry) { + $query = $this->getQueryBuilder(); + $query->delete('filecache') + ->whereFileId($entry->getId()); + $query->execute(); + + $query = $this->getQueryBuilder(); + $query->delete('filecache_extended') + ->whereFileId($entry->getId()); + $query->execute(); + + if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) { + $this->removeChildren($entry); + } + } + } + + /** + * Get all sub folders of a folder + * + * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for + * @return ICacheEntry[] the cache entries for the subfolders + */ + private function getSubFolders(ICacheEntry $entry) { + $children = $this->getFolderContentsById($entry->getId()); + return array_filter($children, function ($child) { + return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; + }); + } + + /** + * Recursively remove all children of a folder + * + * @param ICacheEntry $entry the cache entry of the folder to remove the children of + * @throws \OC\DatabaseException + */ + private function removeChildren(ICacheEntry $entry) { + $parentIds = [$entry->getId()]; + $queue = [$entry->getId()]; + + // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk + // and collecting all folder ids to later use to delete the filecache entries + while ($entryId = array_pop($queue)) { + $children = $this->getFolderContentsById($entryId); + $childIds = array_map(function (ICacheEntry $cacheEntry) { + return $cacheEntry->getId(); + }, $children); + + $query = $this->getQueryBuilder(); + $query->delete('filecache_extended') + ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY))); + $query->execute(); + + /** @var ICacheEntry[] $childFolders */ + $childFolders = array_filter($children, function ($child) { + return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; + }); + foreach ($childFolders as $folder) { + $parentIds[] = $folder->getId(); + $queue[] = $folder->getId(); + } + } + + $query = $this->getQueryBuilder(); + $query->delete('filecache') + ->whereParentIn($parentIds); + $query->execute(); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->moveFromCache($this, $source, $target); + } + + /** + * Get the storage id and path needed for a move + * + * @param string $path + * @return array [$storageId, $internalPath] + */ + protected function getMoveInfo($path) { + return [$this->getNumericStorageId(), $path]; + } + + /** + * Move a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + * @throws \OC\DatabaseException + * @throws \Exception if the given storages have an invalid id + */ + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + if ($sourceCache instanceof Cache) { + // normalize source and target + $sourcePath = $this->normalize($sourcePath); + $targetPath = $this->normalize($targetPath); + + $sourceData = $sourceCache->get($sourcePath); + $sourceId = $sourceData['fileid']; + $newParentId = $this->getParentId($targetPath); + + [$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath); + [$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath); + + if (is_null($sourceStorageId) || $sourceStorageId === false) { + throw new \Exception('Invalid source storage id: ' . $sourceStorageId); + } + if (is_null($targetStorageId) || $targetStorageId === false) { + throw new \Exception('Invalid target storage id: ' . $targetStorageId); + } + + $this->connection->beginTransaction(); + if ($sourceData['mimetype'] === 'httpd/unix-directory') { + //update all child entries + $sourceLength = mb_strlen($sourcePath); + $query = $this->connection->getQueryBuilder(); + + $fun = $query->func(); + $newPathFunction = $fun->concat( + $query->createNamedParameter($targetPath), + $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash + ); + $query->update('filecache') + ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT)) + ->set('path_hash', $fun->md5($newPathFunction)) + ->set('path', $newPathFunction) + ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%'))); + + try { + $query->execute(); + } catch (\OC\DatabaseException $e) { + $this->connection->rollBack(); + throw $e; + } + } + + $query = $this->getQueryBuilder(); + $query->update('filecache') + ->set('storage', $query->createNamedParameter($targetStorageId)) + ->set('path', $query->createNamedParameter($targetPath)) + ->set('path_hash', $query->createNamedParameter(md5($targetPath))) + ->set('name', $query->createNamedParameter(basename($targetPath))) + ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT)) + ->whereFileId($sourceId); + $query->execute(); + + $this->connection->commit(); + } else { + $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath); + } + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $query = $this->getQueryBuilder(); + $query->delete('filecache') + ->whereStorageId(); + $query->execute(); + + $query = $this->connection->getQueryBuilder(); + $query->delete('storages') + ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId))); + $query->execute(); + } + + /** + * Get the scan status of a file + * + * - Cache::NOT_FOUND: File is not in the cache + * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known + * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned + * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned + * + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + // normalize file + $file = $this->normalize($file); + + $query = $this->getQueryBuilder(); + $query->select('size') + ->from('filecache') + ->whereStorageId() + ->wherePath($file); + $size = $query->execute()->fetchColumn(); + if ($size !== false) { + if ((int)$size === -1) { + return self::SHALLOW; + } else { + return self::COMPLETE; + } + } else { + if (isset($this->partial[$file])) { + return self::PARTIAL; + } else { + return self::NOT_FOUND; + } + } + } + + /** + * search for files matching $pattern + * + * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') + * @return ICacheEntry[] an array of cache entries where the name matches the search pattern + */ + public function search($pattern) { + // normalize pattern + $pattern = $this->normalize($pattern); + + if ($pattern === '%%') { + return []; + } + + $query = $this->getQueryBuilder(); + $query->selectFileCache() + ->whereStorageId() + ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern))); + + return array_map(function (array $data) { + return self::cacheEntryFromData($data, $this->mimetypeLoader); + }, $query->execute()->fetchAll()); + } + + /** + * @param Statement $result + * @return CacheEntry[] + */ + private function searchResultToCacheEntries(Statement $result) { + $files = $result->fetchAll(); + + return array_map(function (array $data) { + return self::cacheEntryFromData($data, $this->mimetypeLoader); + }, $files); + } + + /** + * search for files by mimetype + * + * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') + * where it will search for all mimetypes in the group ('image/*') + * @return ICacheEntry[] an array of cache entries where the mimetype matches the search + */ + public function searchByMime($mimetype) { + $mimeId = $this->mimetypeLoader->getId($mimetype); + + $query = $this->getQueryBuilder(); + $query->selectFileCache() + ->whereStorageId(); + + if (strpos($mimetype, '/')) { + $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT))); + } else { + $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT))); + } + + return array_map(function (array $data) { + return self::cacheEntryFromData($data, $this->mimetypeLoader); + }, $query->execute()->fetchAll()); + } + + public function searchQuery(ISearchQuery $searchQuery) { + $builder = $this->getQueryBuilder(); + + $query = $builder->selectFileCache('file'); + + $query->whereStorageId(); + + if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) { + $query + ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) + ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( + $builder->expr()->eq('tagmap.type', 'tag.type'), + $builder->expr()->eq('tagmap.categoryid', 'tag.id') + )) + ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files'))) + ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID()))); + } + + $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()); + if ($searchExpr) { + $query->andWhere($searchExpr); + } + + if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) { + $query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%'))); + } + + $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder()); + + if ($searchQuery->getLimit()) { + $query->setMaxResults($searchQuery->getLimit()); + } + if ($searchQuery->getOffset()) { + $query->setFirstResult($searchQuery->getOffset()); + } + + $result = $query->execute(); + return $this->searchResultToCacheEntries($result); + } + + /** + * Re-calculate the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { + $this->calculateFolderSize($path, $data); + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + if ($isBackgroundScan) { + $parentData = $this->get($parent); + if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) { + $this->correctFolderSize($parent, $parentData, $isBackgroundScan); + } + } else { + $this->correctFolderSize($parent); + } + } + } + + /** + * get the incomplete count that shares parent $folder + * + * @param int $fileId the file id of the folder + * @return int + */ + public function getIncompleteChildrenCount($fileId) { + if ($fileId > -1) { + $query = $this->getQueryBuilder(); + $query->select($query->func()->count()) + ->from('filecache') + ->whereParent($fileId) + ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + + return (int)$query->execute()->fetchColumn(); + } + return -1; + } + + /** + * calculate the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + $totalSize = 0; + if (is_null($entry) or !isset($entry['fileid'])) { + $entry = $this->get($path); + } + if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) { + $id = $entry['fileid']; + + $query = $this->getQueryBuilder(); + $query->selectAlias($query->func()->sum('size'), 'f1') + ->selectAlias($query->func()->min('size'), 'f2') + ->from('filecache') + ->whereStorageId() + ->whereParent($id); + + if ($row = $query->execute()->fetch()) { + [$sum, $min] = array_values($row); + $sum = 0 + $sum; + $min = 0 + $min; + if ($min === -1) { + $totalSize = $min; + } else { + $totalSize = $sum; + } + if ($entry['size'] !== $totalSize) { + $this->update($id, ['size' => $totalSize]); + } + } + } + return $totalSize; + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + $query = $this->getQueryBuilder(); + $query->select('fileid') + ->from('filecache') + ->whereStorageId(); + + return array_map(function ($id) { + return (int)$id; + }, $query->execute()->fetchAll(\PDO::FETCH_COLUMN)); + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + $query = $this->getQueryBuilder(); + $query->select('path') + ->from('filecache') + ->whereStorageId() + ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->orderBy('fileid', 'DESC'); + + return $query->execute()->fetchColumn(); + } + + /** + * get the path of a file on this storage by it's file id + * + * @param int $id the file id of the file or folder to search + * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache + */ + public function getPathById($id) { + $query = $this->getQueryBuilder(); + $query->select('path') + ->from('filecache') + ->whereStorageId() + ->whereFileId($id); + + $path = $query->execute()->fetchColumn(); + return $path === false ? null : $path; + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * unlike getPathById this does not limit the search to files on this storage and + * instead does a global search in the cache table + * + * @param int $id + * @return array first element holding the storage id, second the path + * @deprecated use getPathById() instead + */ + public static function getById($id) { + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->select('path', 'storage') + ->from('filecache') + ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + if ($row = $query->execute()->fetch()) { + $numericId = $row['storage']; + $path = $row['path']; + } else { + return null; + } + + if ($id = Storage::getStorageId($numericId)) { + return [$id, $path]; + } else { + return null; + } + } + + /** + * normalize the given path + * + * @param string $path + * @return string + */ + public function normalize($path) { + return trim(\OC_Util::normalizeUnicode($path), '/'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheEntry.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheEntry.php new file mode 100644 index 0000000..744b88b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheEntry.php @@ -0,0 +1,127 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; + +/** + * meta data for a file or folder + */ +class CacheEntry implements ICacheEntry, \ArrayAccess { + /** + * @var array + */ + private $data; + + public function __construct(array $data) { + $this->data = $data; + } + + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + public function offsetGet($offset) { + if (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + public function getId() { + return (int)$this->data['fileid']; + } + + public function getStorageId() { + return $this->data['storage']; + } + + + public function getPath() { + return $this->data['path']; + } + + + public function getName() { + return $this->data['name']; + } + + + public function getMimeType() { + return $this->data['mimetype']; + } + + + public function getMimePart() { + return $this->data['mimepart']; + } + + public function getSize() { + return $this->data['size']; + } + + public function getMTime() { + return $this->data['mtime']; + } + + public function getStorageMTime() { + return $this->data['storage_mtime']; + } + + public function getEtag() { + return $this->data['etag']; + } + + public function getPermissions() { + return $this->data['permissions']; + } + + public function isEncrypted() { + return isset($this->data['encrypted']) && $this->data['encrypted']; + } + + public function getMetadataEtag(): ?string { + return $this->data['metadata_etag']; + } + + public function getCreationTime(): ?int { + return $this->data['creation_time']; + } + + public function getUploadTime(): ?int { + return $this->data['upload_time']; + } + + public function getData() { + return $this->data; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheQueryBuilder.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheQueryBuilder.php new file mode 100644 index 0000000..ac17cfa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/CacheQueryBuilder.php @@ -0,0 +1,110 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +use OC\DB\QueryBuilder\QueryBuilder; +use OC\SystemConfig; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\ILogger; + +/** + * Query builder with commonly used helpers for filecache queries + */ +class CacheQueryBuilder extends QueryBuilder { + private $cache; + private $alias = null; + + public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger, Cache $cache) { + parent::__construct($connection, $systemConfig, $logger); + + $this->cache = $cache; + } + + public function selectFileCache(string $alias = null) { + $name = $alias ? $alias : 'filecache'; + $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", 'name', 'mimetype', 'mimepart', 'size', 'mtime', + 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time') + ->from('filecache', $name) + ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + + $this->alias = $name; + + return $this; + } + + public function whereStorageId() { + $this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($this->cache->getNumericStorageId(), IQueryBuilder::PARAM_INT))); + + return $this; + } + + public function whereFileId(int $fileId) { + $alias = $this->alias; + if ($alias) { + $alias .= '.'; + } else { + $alias = ''; + } + + $this->andWhere($this->expr()->eq("{$alias}fileid", $this->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + + return $this; + } + + public function wherePath(string $path) { + $this->andWhere($this->expr()->eq('path_hash', $this->createNamedParameter(md5($path)))); + + return $this; + } + + public function whereParent(int $parent) { + $alias = $this->alias; + if ($alias) { + $alias .= '.'; + } else { + $alias = ''; + } + + $this->andWhere($this->expr()->eq("{$alias}parent", $this->createNamedParameter($parent, IQueryBuilder::PARAM_INT))); + + return $this; + } + + public function whereParentIn(array $parents) { + $alias = $this->alias; + if ($alias) { + $alias .= '.'; + } else { + $alias = ''; + } + + $this->andWhere($this->expr()->in("{$alias}parent", $this->createNamedParameter($parents, IQueryBuilder::PARAM_INT_ARRAY))); + + return $this; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/FailedCache.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/FailedCache.php new file mode 100644 index 0000000..b57ca2b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/FailedCache.php @@ -0,0 +1,137 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\Search\ISearchQuery; + +/** + * Storage placeholder to represent a missing precondition, storage unavailable + */ +class FailedCache implements ICache { + /** @var bool whether to show the failed storage in the ui */ + private $visible; + + /** + * FailedCache constructor. + * + * @param bool $visible + */ + public function __construct($visible = true) { + $this->visible = $visible; + } + + + public function getNumericStorageId() { + return -1; + } + + public function get($file) { + if ($file === '') { + return new CacheEntry([ + 'fileid' => -1, + 'size' => 0, + 'mimetype' => 'httpd/unix-directory', + 'mimepart' => 'httpd', + 'permissions' => $this->visible ? Constants::PERMISSION_READ : 0, + 'mtime' => time() + ]); + } else { + return false; + } + } + + public function getFolderContents($folder) { + return []; + } + + public function getFolderContentsById($fileId) { + return []; + } + + public function put($file, array $data) { + } + + public function insert($file, array $data) { + } + + public function update($id, array $data) { + } + + public function getId($file) { + return -1; + } + + public function getParentId($file) { + return -1; + } + + public function inCache($file) { + return false; + } + + public function remove($file) { + } + + public function move($source, $target) { + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + } + + public function clear() { + } + + public function getStatus($file) { + return ICache::NOT_FOUND; + } + + public function search($pattern) { + return []; + } + + public function searchByMime($mimetype) { + return []; + } + + public function searchQuery(ISearchQuery $query) { + return []; + } + + public function getAll() { + return []; + } + + public function getIncomplete() { + return []; + } + + public function getPathById($id) { + return null; + } + + public function normalize($path) { + return $path; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomeCache.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomeCache.php new file mode 100644 index 0000000..b86a31f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomeCache.php @@ -0,0 +1,88 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; + +class HomeCache extends Cache { + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + if ($path !== '/' and $path !== '' and $path !== 'files' and $path !== 'files_trashbin' and $path !== 'files_versions') { + return parent::calculateFolderSize($path, $entry); + } elseif ($path === '' or $path === '/') { + // since the size of / isn't used (the size of /files is used instead) there is no use in calculating it + return 0; + } + + $totalSize = 0; + if (is_null($entry)) { + $entry = $this->get($path); + } + if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { + $id = $entry['fileid']; + $sql = 'SELECT SUM(`size`) AS f1 ' . + 'FROM `*PREFIX*filecache` ' . + 'WHERE `parent` = ? AND `storage` = ? AND `size` >= 0'; + $result = \OC_DB::executeAudited($sql, [$id, $this->getNumericStorageId()]); + if ($row = $result->fetchRow()) { + $result->closeCursor(); + list($sum) = array_values($row); + $totalSize = 0 + $sum; + $entry['size'] += 0; + if ($entry['size'] !== $totalSize) { + $this->update($id, ['size' => $totalSize]); + } + } + } + return $totalSize; + } + + /** + * @param string $path + * @return ICacheEntry + */ + public function get($path) { + $data = parent::get($path); + if ($path === '' or $path === '/') { + // only the size of the "files" dir counts + $filesData = parent::get('files'); + + if (isset($filesData['size'])) { + $data['size'] = $filesData['size']; + } + } + return $data; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomePropagator.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomePropagator.php new file mode 100644 index 0000000..ab78b1d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/HomePropagator.php @@ -0,0 +1,52 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\IDBConnection; + +class HomePropagator extends Propagator { + private $ignoredBaseFolders; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection) { + parent::__construct($storage, $connection); + $this->ignoredBaseFolders = ['files_encryption']; + } + + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference number of bytes the file has grown + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + list($baseFolder) = explode('/', $internalPath, 2); + if (in_array($baseFolder, $this->ignoredBaseFolders)) { + return []; + } else { + parent::propagateChange($internalPath, $time, $sizeDifference); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/LocalRootScanner.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/LocalRootScanner.php new file mode 100644 index 0000000..a10f511 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/LocalRootScanner.php @@ -0,0 +1,50 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +class LocalRootScanner extends Scanner { + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) { + if ($this->shouldScanPath($file)) { + return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock, $data); + } else { + return null; + } + } + + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { + if ($this->shouldScanPath($path)) { + return parent::scan($path, $recursive, $reuse, $lock); + } else { + return null; + } + } + + private function shouldScanPath(string $path): bool { + $path = trim($path, '/'); + return $path === '' || strpos($path, 'appdata_') === 0 || strpos($path, '__groupfolders') === 0; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/MoveFromCacheTrait.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/MoveFromCacheTrait.php new file mode 100644 index 0000000..0f42b00 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/MoveFromCacheTrait.php @@ -0,0 +1,92 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; + +/** + * Fallback implementation for moveFromCache + */ +trait MoveFromCacheTrait { + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + abstract public function put($file, array $data); + + /** + * Move a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + */ + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + $sourceEntry = $sourceCache->get($sourcePath); + + $this->copyFromCache($sourceCache, $sourceEntry, $targetPath); + + $sourceCache->remove($sourcePath); + } + + /** + * Copy a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param ICacheEntry $sourceEntry + * @param string $targetPath + */ + public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath) { + $this->put($targetPath, $this->cacheEntryToArray($sourceEntry)); + if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) { + $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId()); + foreach ($folderContent as $subEntry) { + $subTargetPath = $targetPath . '/' . $subEntry->getName(); + $this->copyFromCache($sourceCache, $subEntry, $subTargetPath); + } + } + } + + private function cacheEntryToArray(ICacheEntry $entry) { + return [ + 'size' => $entry->getSize(), + 'mtime' => $entry->getMTime(), + 'storage_mtime' => $entry->getStorageMTime(), + 'mimetype' => $entry->getMimeType(), + 'mimepart' => $entry->getMimePart(), + 'etag' => $entry->getEtag(), + 'permissions' => $entry->getPermissions(), + 'encrypted' => $entry->isEncrypted(), + 'creation_time' => $entry->getCreationTime(), + 'upload_time' => $entry->getUploadTime(), + 'metadata_etag' => $entry->getMetadataEtag(), + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/NullWatcher.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/NullWatcher.php new file mode 100644 index 0000000..57e4344 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/NullWatcher.php @@ -0,0 +1,53 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +class NullWatcher extends Watcher { + private $policy; + + public function __construct() { + } + + public function setPolicy($policy) { + $this->policy = $policy; + } + + public function getPolicy() { + return $this->policy; + } + + public function checkUpdate($path, $cachedEntry = null) { + return false; + } + + public function update($path, $cachedData) { + } + + public function needsUpdate($path, $cachedData) { + return false; + } + + public function cleanFolder($path) { + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Propagator.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Propagator.php new file mode 100644 index 0000000..c9200d3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Propagator.php @@ -0,0 +1,201 @@ + + * @author Lukas Reschke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Cache\IPropagator; +use OCP\IDBConnection; + +/** + * Propagate etags and mtimes within the storage + */ +class Propagator implements IPropagator { + private $inBatch = false; + + private $batch = []; + + /** + * @var \OC\Files\Storage\Storage + */ + protected $storage; + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var array + */ + private $ignore = []; + + public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection, array $ignore = []) { + $this->storage = $storage; + $this->connection = $connection; + $this->ignore = $ignore; + } + + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference number of bytes the file has grown + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + // Do not propogate changes in ignored paths + foreach ($this->ignore as $ignore) { + if (strpos($internalPath, $ignore) === 0) { + return; + } + } + + $storageId = (int)$this->storage->getStorageCache()->getNumericId(); + + $parents = $this->getParents($internalPath); + + if ($this->inBatch) { + foreach ($parents as $parent) { + $this->addToBatch($parent, $time, $sizeDifference); + } + return; + } + + $parentHashes = array_map('md5', $parents); + $etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag + + $builder = $this->connection->getQueryBuilder(); + $hashParams = array_map(function ($hash) use ($builder) { + return $builder->expr()->literal($hash); + }, $parentHashes); + + $builder->update('filecache') + ->set('mtime', $builder->func()->greatest('mtime', $builder->createNamedParameter((int)$time, IQueryBuilder::PARAM_INT))) + ->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR)) + ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($builder->expr()->in('path_hash', $hashParams)); + + $builder->execute(); + + if ($sizeDifference !== 0) { + // we need to do size separably so we can ignore entries with uncalculated size + $builder = $this->connection->getQueryBuilder(); + $builder->update('filecache') + ->set('size', $builder->func()->greatest( + $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT), + $builder->func()->add('size', $builder->createNamedParameter($sizeDifference))) + ) + ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($builder->expr()->in('path_hash', $hashParams)) + ->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + + $builder->execute(); + } + } + + protected function getParents($path) { + $parts = explode('/', $path); + $parent = ''; + $parents = []; + foreach ($parts as $part) { + $parents[] = $parent; + $parent = trim($parent . '/' . $part, '/'); + } + return $parents; + } + + /** + * Mark the beginning of a propagation batch + * + * Note that not all cache setups support propagation in which case this will be a noop + * + * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent + * before the batch is committed. + */ + public function beginBatch() { + $this->inBatch = true; + } + + private function addToBatch($internalPath, $time, $sizeDifference) { + if (!isset($this->batch[$internalPath])) { + $this->batch[$internalPath] = [ + 'hash' => md5($internalPath), + 'time' => $time, + 'size' => $sizeDifference + ]; + } else { + $this->batch[$internalPath]['size'] += $sizeDifference; + if ($time > $this->batch[$internalPath]['time']) { + $this->batch[$internalPath]['time'] = $time; + } + } + } + + /** + * Commit the active propagation batch + */ + public function commitBatch() { + if (!$this->inBatch) { + throw new \BadMethodCallException('Not in batch'); + } + $this->inBatch = false; + + $this->connection->beginTransaction(); + + $query = $this->connection->getQueryBuilder(); + $storageId = (int)$this->storage->getStorageCache()->getNumericId(); + + $query->update('filecache') + ->set('mtime', $query->createFunction('GREATEST(' . $query->getColumnName('mtime') . ', ' . $query->createParameter('time') . ')')) + ->set('etag', $query->expr()->literal(uniqid())) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))); + + $sizeQuery = $this->connection->getQueryBuilder(); + $sizeQuery->update('filecache') + ->set('size', $sizeQuery->func()->add('size', $sizeQuery->createParameter('size'))) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))) + ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + + foreach ($this->batch as $item) { + $query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT); + $query->setParameter('hash', $item['hash']); + + $query->execute(); + + if ($item['size']) { + $sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT); + $sizeQuery->setParameter('hash', $item['hash']); + + $sizeQuery->execute(); + } + } + + $this->batch = []; + + $this->connection->commit(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/QuerySearchHelper.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/QuerySearchHelper.php new file mode 100644 index 0000000..574b4c1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/QuerySearchHelper.php @@ -0,0 +1,226 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Tobias Kaminsky + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOperator; +use OCP\Files\Search\ISearchOrder; + +/** + * Tools for transforming search queries into database queries + */ +class QuerySearchHelper { + protected static $searchOperatorMap = [ + ISearchComparison::COMPARE_LIKE => 'iLike', + ISearchComparison::COMPARE_EQUAL => 'eq', + ISearchComparison::COMPARE_GREATER_THAN => 'gt', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte', + ISearchComparison::COMPARE_LESS_THAN => 'lt', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte' + ]; + + protected static $searchOperatorNegativeMap = [ + ISearchComparison::COMPARE_LIKE => 'notLike', + ISearchComparison::COMPARE_EQUAL => 'neq', + ISearchComparison::COMPARE_GREATER_THAN => 'lte', + ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt', + ISearchComparison::COMPARE_LESS_THAN => 'gte', + ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt' + ]; + + public const TAG_FAVORITE = '_$!!$_'; + + /** @var IMimeTypeLoader */ + private $mimetypeLoader; + + /** + * QuerySearchUtil constructor. + * + * @param IMimeTypeLoader $mimetypeLoader + */ + public function __construct(IMimeTypeLoader $mimetypeLoader) { + $this->mimetypeLoader = $mimetypeLoader; + } + + /** + * Whether or not the tag tables should be joined to complete the search + * + * @param ISearchOperator $operator + * @return boolean + */ + public function shouldJoinTags(ISearchOperator $operator) { + if ($operator instanceof ISearchBinaryOperator) { + return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) { + return $shouldJoin || $this->shouldJoinTags($operator); + }, false); + } elseif ($operator instanceof ISearchComparison) { + return $operator->getField() === 'tagname' || $operator->getField() === 'favorite'; + } + return false; + } + + /** + * @param IQueryBuilder $builder + * @param ISearchOperator $operator + */ + public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) { + return array_filter(array_map(function ($operator) use ($builder) { + return $this->searchOperatorToDBExpr($builder, $operator); + }, $operators)); + } + + public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) { + $expr = $builder->expr(); + if ($operator instanceof ISearchBinaryOperator) { + if (count($operator->getArguments()) === 0) { + return null; + } + + switch ($operator->getType()) { + case ISearchBinaryOperator::OPERATOR_NOT: + $negativeOperator = $operator->getArguments()[0]; + if ($negativeOperator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap); + } else { + throw new \InvalidArgumentException('Binary operators inside "not" is not supported'); + } + // no break + case ISearchBinaryOperator::OPERATOR_AND: + return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments())); + case ISearchBinaryOperator::OPERATOR_OR: + return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments())); + default: + throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType()); + } + } elseif ($operator instanceof ISearchComparison) { + return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator)); + } + } + + private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) { + $this->validateComparison($comparison); + + list($field, $value, $type) = $this->getOperatorFieldAndValue($comparison); + if (isset($operatorMap[$type])) { + $queryOperator = $operatorMap[$type]; + return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value)); + } else { + throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType()); + } + } + + private function getOperatorFieldAndValue(ISearchComparison $operator) { + $field = $operator->getField(); + $value = $operator->getValue(); + $type = $operator->getType(); + if ($field === 'mimetype') { + if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) { + $value = (int)$this->mimetypeLoader->getId($value); + } elseif ($operator->getType() === ISearchComparison::COMPARE_LIKE) { + // transform "mimetype='foo/%'" to "mimepart='foo'" + if (preg_match('|(.+)/%|', $value, $matches)) { + $field = 'mimepart'; + $value = (int)$this->mimetypeLoader->getId($matches[1]); + $type = ISearchComparison::COMPARE_EQUAL; + } elseif (strpos($value, '%') !== false) { + throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported'); + } else { + $field = 'mimetype'; + $value = (int)$this->mimetypeLoader->getId($value); + $type = ISearchComparison::COMPARE_EQUAL; + } + } + } elseif ($field === 'favorite') { + $field = 'tag.category'; + $value = self::TAG_FAVORITE; + } elseif ($field === 'tagname') { + $field = 'tag.category'; + } elseif ($field === 'fileid') { + $field = 'file.fileid'; + } + return [$field, $value, $type]; + } + + private function validateComparison(ISearchComparison $operator) { + $types = [ + 'mimetype' => 'string', + 'mtime' => 'integer', + 'name' => 'string', + 'size' => 'integer', + 'tagname' => 'string', + 'favorite' => 'boolean', + 'fileid' => 'integer' + ]; + $comparisons = [ + 'mimetype' => ['eq', 'like'], + 'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'], + 'name' => ['eq', 'like'], + 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'], + 'tagname' => ['eq', 'like'], + 'favorite' => ['eq'], + 'fileid' => ['eq'] + ]; + + if (!isset($types[$operator->getField()])) { + throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField()); + } + $type = $types[$operator->getField()]; + if (gettype($operator->getValue()) !== $type) { + throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField()); + } + if (!in_array($operator->getType(), $comparisons[$operator->getField()])) { + throw new \InvalidArgumentException('Unsupported comparison for field ' . $operator->getField() . ': ' . $operator->getType()); + } + } + + private function getParameterForValue(IQueryBuilder $builder, $value) { + if ($value instanceof \DateTime) { + $value = $value->getTimestamp(); + } + if (is_numeric($value)) { + $type = IQueryBuilder::PARAM_INT; + } else { + $type = IQueryBuilder::PARAM_STR; + } + return $builder->createNamedParameter($value, $type); + } + + /** + * @param IQueryBuilder $query + * @param ISearchOrder[] $orders + */ + public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) { + foreach ($orders as $order) { + $query->addOrderBy($order->getField(), $order->getDirection()); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Scanner.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Scanner.php new file mode 100644 index 0000000..f895948 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Scanner.php @@ -0,0 +1,541 @@ + + * @author Arthur Schiwon + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Jagszent + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Martin Mattel + * @author Morris Jobke + * @author Owen Winkler + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OC\Files\Filesystem; +use OC\Hooks\BasicEmitter; +use OCP\Files\Cache\IScanner; +use OCP\Files\ForbiddenException; +use OCP\ILogger; +use OCP\Lock\ILockingProvider; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Files\Cache\Scanner: + * - scanFile(string $path, string $storageId) + * - scanFolder(string $path, string $storageId) + * - postScanFile(string $path, string $storageId) + * - postScanFolder(string $path, string $storageId) + * + * @package OC\Files\Cache + */ +class Scanner extends BasicEmitter implements IScanner { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + /** + * @var string $storageId + */ + protected $storageId; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + protected $cache; + + /** + * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache + */ + protected $cacheActive; + + /** + * @var bool $useTransactions whether to use transactions + */ + protected $useTransactions = true; + + /** + * @var \OCP\Lock\ILockingProvider + */ + protected $lockingProvider; + + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->storageId = $this->storage->getId(); + $this->cache = $storage->getCache(); + $this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false); + $this->lockingProvider = \OC::$server->getLockingProvider(); + } + + /** + * Whether to wrap the scanning of a folder in a database transaction + * On default transactions are used + * + * @param bool $useTransactions + */ + public function setUseTransactions($useTransactions) { + $this->useTransactions = $useTransactions; + } + + /** + * get all the metadata of a file or folder + * * + * + * @param string $path + * @return array an array of metadata of the file + */ + protected function getData($path) { + $data = $this->storage->getMetaData($path); + if (is_null($data)) { + \OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG); + } + return $data; + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param int $parentId + * @param array|null|false $cacheData existing data in the cache for the file to be scanned + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @param null $data the metadata for the file, as returned by the storage + * @return array an array of metadata of the scanned file + * @throws \OCP\Lock\LockedException + */ + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) { + if ($file !== '') { + try { + $this->storage->verifyPath(dirname($file), basename($file)); + } catch (\Exception $e) { + return null; + } + } + // only proceed if $file is not a partial file nor a blacklisted file + if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) { + + //acquire a lock + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + + try { + $data = $data ?? $this->getData($file); + } catch (ForbiddenException $e) { + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + + return null; + } + + try { + if ($data) { + + // pre-emit only if it was a file. By that we avoid counting/treating folders as files + if ($data['mimetype'] !== 'httpd/unix-directory') { + $this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]); + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]); + } + + $parent = dirname($file); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + if ($parentId === -1) { + $parentId = $this->cache->getParentId($file); + } + + // scan the parent if it's not in the cache (id -1) and the current file is not the root folder + if ($file and $parentId === -1) { + $parentData = $this->scanFile($parent); + if (!$parentData) { + return null; + } + $parentId = $parentData['fileid']; + } + if ($parent) { + $data['parent'] = $parentId; + } + if (is_null($cacheData)) { + /** @var CacheEntry $cacheData */ + $cacheData = $this->cache->get($file); + } + if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) { + // prevent empty etag + if (empty($cacheData['etag'])) { + $etag = $data['etag']; + } else { + $etag = $cacheData['etag']; + } + $fileId = $cacheData['fileid']; + $data['fileid'] = $fileId; + // only reuse data if the file hasn't explicitly changed + if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) { + $data['mtime'] = $cacheData['mtime']; + if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) { + $data['size'] = $cacheData['size']; + } + if ($reuseExisting & self::REUSE_ETAG) { + $data['etag'] = $etag; + } + } + // Only update metadata that has changed + $newData = array_diff_assoc($data, $cacheData->getData()); + } else { + $newData = $data; + $fileId = -1; + } + if (!empty($newData)) { + // Reset the checksum if the data has changed + $newData['checksum'] = ''; + $newData['parent'] = $parentId; + $data['fileid'] = $this->addToCache($file, $newData, $fileId); + } + if ($cacheData && isset($cacheData['size'])) { + $data['oldSize'] = $cacheData['size']; + } else { + $data['oldSize'] = 0; + } + + if ($cacheData && isset($cacheData['encrypted'])) { + $data['encrypted'] = $cacheData['encrypted']; + } + + // post-emit only if it was a file. By that we avoid counting/treating folders as files + if ($data['mimetype'] !== 'httpd/unix-directory') { + $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]); + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]); + } + } else { + $this->removeFromCache($file); + } + } catch (\Exception $e) { + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + throw $e; + } + + //release the acquired lock + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + + if ($data && !isset($data['encrypted'])) { + $data['encrypted'] = false; + } + return $data; + } + + return null; + } + + protected function removeFromCache($path) { + \OC_Hook::emit('Scanner', 'removeFromCache', ['file' => $path]); + $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', [$path]); + if ($this->cacheActive) { + $this->cache->remove($path); + } + } + + /** + * @param string $path + * @param array $data + * @param int $fileId + * @return int the id of the added file + */ + protected function addToCache($path, $data, $fileId = -1) { + if (isset($data['scan_permissions'])) { + $data['permissions'] = $data['scan_permissions']; + } + \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]); + $this->emit('\OC\Files\Cache\Scanner', 'addToCache', [$path, $this->storageId, $data]); + if ($this->cacheActive) { + if ($fileId !== -1) { + $this->cache->update($fileId, $data); + return $fileId; + } else { + return $this->cache->insert($path, $data); + } + } else { + return -1; + } + } + + /** + * @param string $path + * @param array $data + * @param int $fileId + */ + protected function updateCache($path, $data, $fileId = -1) { + \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]); + $this->emit('\OC\Files\Cache\Scanner', 'updateCache', [$path, $this->storageId, $data]); + if ($this->cacheActive) { + if ($fileId !== -1) { + $this->cache->update($fileId, $data); + } else { + $this->cache->put($path, $data); + } + } + } + + /** + * scan a folder and all it's children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return array an array of the meta data of the scanned file or folder + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; + } + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider); + $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + try { + $data = $this->scanFile($path, $reuse, -1, null, $lock); + if ($data and $data['mimetype'] === 'httpd/unix-directory') { + $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock); + $data['size'] = $size; + } + } finally { + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider); + } + } + } + return $data; + } + + /** + * Get the children currently in the cache + * + * @param int $folderId + * @return array[] + */ + protected function getExistingChildren($folderId) { + $existingChildren = []; + $children = $this->cache->getFolderContentsById($folderId); + foreach ($children as $child) { + $existingChildren[$child['name']] = $child; + } + return $existingChildren; + } + + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param int $folderId id for the folder to be scanned + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; + } + $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]); + $size = 0; + if (!is_null($folderId)) { + $folderId = $this->cache->getId($path); + } + $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size); + + foreach ($childQueue as $child => $childId) { + $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock); + if ($childSize === -1) { + $size = -1; + } elseif ($size !== -1) { + $size += $childSize; + } + } + if ($this->cacheActive) { + $this->cache->update($folderId, ['size' => $size]); + } + $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]); + return $size; + } + + private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) { + // we put this in it's own function so it cleans up the memory before we start recursing + $existingChildren = $this->getExistingChildren($folderId); + $newChildren = iterator_to_array($this->storage->getDirectoryContent($path)); + + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->beginTransaction(); + } + + $exceptionOccurred = false; + $childQueue = []; + $newChildNames = []; + foreach ($newChildren as $fileMeta) { + $file = $fileMeta['name']; + $newChildNames[] = $file; + $child = $path ? $path . '/' . $file : $file; + try { + $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false; + $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta); + if ($data) { + if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) { + $childQueue[$child] = $data['fileid']; + } elseif ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) { + // only recurse into folders which aren't fully scanned + $childQueue[$child] = $data['fileid']; + } elseif ($data['size'] === -1) { + $size = -1; + } elseif ($size !== -1) { + $size += $data['size']; + } + } + } catch (\Doctrine\DBAL\DBALException $ex) { + // might happen if inserting duplicate while a scanning + // process is running in parallel + // log and ignore + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->rollback(); + \OC::$server->getDatabaseConnection()->beginTransaction(); + } + \OC::$server->getLogger()->logException($ex, [ + 'message' => 'Exception while scanning file "' . $child . '"', + 'level' => ILogger::DEBUG, + 'app' => 'core', + ]); + $exceptionOccurred = true; + } catch (\OCP\Lock\LockedException $e) { + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->rollback(); + } + throw $e; + } + } + $removedChildren = \array_diff(array_keys($existingChildren), $newChildNames); + foreach ($removedChildren as $childName) { + $child = $path ? $path . '/' . $childName : $childName; + $this->removeFromCache($child); + } + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->commit(); + } + if ($exceptionOccurred) { + // It might happen that the parallel scan process has already + // inserted mimetypes but those weren't available yet inside the transaction + // To make sure to have the updated mime types in such cases, + // we reload them here + \OC::$server->getMimeTypeLoader()->reset(); + } + return $childQueue; + } + + /** + * check if the file should be ignored when scanning + * NOTE: files with a '.part' extension are ignored as well! + * prevents unfinished put requests to be scanned + * + * @param string $file + * @return boolean + */ + public static function isPartialFile($file) { + if (pathinfo($file, PATHINFO_EXTENSION) === 'part') { + return true; + } + if (strpos($file, '.part/') !== false) { + return true; + } + + return false; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + if (!$this->cache->inCache('')) { + $this->runBackgroundScanJob(function () { + $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG); + }, ''); + } else { + $lastPath = null; + while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) { + $this->runBackgroundScanJob(function () use ($path) { + $this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE); + }, $path); + // FIXME: this won't proceed with the next item, needs revamping of getIncomplete() + // to make this possible + $lastPath = $path; + } + } + } + + private function runBackgroundScanJob(callable $callback, $path) { + try { + $callback(); + \OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]); + if ($this->cacheActive && $this->cache instanceof Cache) { + $this->cache->correctFolderSize($path, null, true); + } + } catch (\OCP\Files\StorageInvalidException $e) { + // skip unavailable storages + } catch (\OCP\Files\StorageNotAvailableException $e) { + // skip unavailable storages + } catch (\OCP\Files\ForbiddenException $e) { + // skip forbidden storages + } catch (\OCP\Lock\LockedException $e) { + // skip unavailable storages + } + } + + /** + * Set whether the cache is affected by scan operations + * + * @param boolean $active The active state of the cache + */ + public function setCacheActive($active) { + $this->cacheActive = $active; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Storage.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Storage.php new file mode 100644 index 0000000..62228e1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Storage.php @@ -0,0 +1,204 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Storage\IStorage; + +/** + * Handle the mapping between the string and numeric storage ids + * + * Each storage has 2 different ids + * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') + * and a numeric storage id which is referenced in the file cache + * + * A mapping between the two storage ids is stored in the database and accessible trough this class + * + * @package OC\Files\Cache + */ +class Storage { + /** @var StorageGlobal|null */ + private static $globalCache = null; + private $storageId; + private $numericId; + + /** + * @return StorageGlobal + */ + public static function getGlobalCache() { + if (is_null(self::$globalCache)) { + self::$globalCache = new StorageGlobal(\OC::$server->getDatabaseConnection()); + } + return self::$globalCache; + } + + /** + * @param \OC\Files\Storage\Storage|string $storage + * @param bool $isAvailable + * @throws \RuntimeException + */ + public function __construct($storage, $isAvailable = true) { + if ($storage instanceof IStorage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + $this->storageId = self::adjustStorageId($this->storageId); + + if ($row = self::getStorageById($this->storageId)) { + $this->numericId = (int)$row['numeric_id']; + } else { + $connection = \OC::$server->getDatabaseConnection(); + $available = $isAvailable ? 1 : 0; + if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) { + $this->numericId = (int)$connection->lastInsertId('*PREFIX*storages'); + } else { + if ($row = self::getStorageById($this->storageId)) { + $this->numericId = (int)$row['numeric_id']; + } else { + throw new \RuntimeException('Storage could neither be inserted nor be selected from the database: ' . $this->storageId); + } + } + } + } + + /** + * @param string $storageId + * @return array + */ + public static function getStorageById($storageId) { + return self::getGlobalCache()->getStorageInfo($storageId); + } + + /** + * Adjusts the storage id to use md5 if too long + * @param string $storageId storage id + * @return string unchanged $storageId if its length is less than 64 characters, + * else returns the md5 of $storageId + */ + public static function adjustStorageId($storageId) { + if (strlen($storageId) > 64) { + return md5($storageId); + } + return $storageId; + } + + /** + * Get the numeric id for the storage + * + * @return int + */ + public function getNumericId() { + return $this->numericId; + } + + /** + * Get the string id for the storage + * + * @param int $numericId + * @return string|null either the storage id string or null if the numeric id is not known + */ + public static function getStorageId($numericId) { + $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'; + $result = \OC_DB::executeAudited($sql, [$numericId]); + if ($row = $result->fetchRow()) { + return $row['id']; + } else { + return null; + } + } + + /** + * Get the numeric of the storage with the provided string id + * + * @param $storageId + * @return int|null either the numeric storage id or null if the storage id is not knwon + */ + public static function getNumericStorageId($storageId) { + $storageId = self::adjustStorageId($storageId); + + if ($row = self::getStorageById($storageId)) { + return (int)$row['numeric_id']; + } else { + return null; + } + } + + /** + * @return array|null [ available, last_checked ] + */ + public function getAvailability() { + if ($row = self::getStorageById($this->storageId)) { + return [ + 'available' => (int)$row['available'] === 1, + 'last_checked' => $row['last_checked'] + ]; + } else { + return null; + } + } + + /** + * @param bool $isAvailable + * @param int $delay amount of seconds to delay reconsidering that storage further + */ + public function setAvailability($isAvailable, int $delay = 0) { + $sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?'; + $available = $isAvailable ? 1 : 0; + \OC_DB::executeAudited($sql, [$available, time() + $delay, $this->storageId]); + } + + /** + * Check if a string storage id is known + * + * @param string $storageId + * @return bool + */ + public static function exists($storageId) { + return !is_null(self::getNumericStorageId($storageId)); + } + + /** + * remove the entry for the storage + * + * @param string $storageId + */ + public static function remove($storageId) { + $storageId = self::adjustStorageId($storageId); + $numericId = self::getNumericStorageId($storageId); + $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; + \OC_DB::executeAudited($sql, [$storageId]); + + if (!is_null($numericId)) { + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; + \OC_DB::executeAudited($sql, [$numericId]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/StorageGlobal.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/StorageGlobal.php new file mode 100644 index 0000000..26a7be2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/StorageGlobal.php @@ -0,0 +1,88 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +/** + * Handle the mapping between the string and numeric storage ids + * + * Each storage has 2 different ids + * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') + * and a numeric storage id which is referenced in the file cache + * + * A mapping between the two storage ids is stored in the database and accessible trough this class + * + * @package OC\Files\Cache + */ +class StorageGlobal { + /** @var IDBConnection */ + private $connection; + + /** @var array[] */ + private $cache = []; + + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @param string[] $storageIds + */ + public function loadForStorageIds(array $storageIds) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) + ->from('storages') + ->where($builder->expr()->in('id', $builder->createNamedParameter(array_values($storageIds), IQueryBuilder::PARAM_STR_ARRAY))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $this->cache[$row['id']] = $row; + } + } + + /** + * @param string $storageId + * @return array|null + */ + public function getStorageInfo($storageId) { + if (!isset($this->cache[$storageId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) + ->from('storages') + ->where($builder->expr()->eq('id', $builder->createNamedParameter($storageId))); + + $row = $query->execute()->fetch(); + if ($row) { + $this->cache[$storageId] = $row; + } + } + return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null; + } + + public function clearCache() { + $this->cache = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Updater.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Updater.php new file mode 100644 index 0000000..79501d9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Updater.php @@ -0,0 +1,261 @@ + + * @author Christoph Wurst + * @author Daniel Jagszent + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OC\Files\FileInfo; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\IUpdater; +use OCP\Files\Storage\IStorage; + +/** + * Update the cache and propagate changes + * + */ +class Updater implements IUpdater { + /** + * @var bool + */ + protected $enabled = true; + + /** + * @var \OC\Files\Storage\Storage + */ + protected $storage; + + /** + * @var \OC\Files\Cache\Propagator + */ + protected $propagator; + + /** + * @var Scanner + */ + protected $scanner; + + /** + * @var Cache + */ + protected $cache; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->propagator = $storage->getPropagator(); + $this->scanner = $storage->getScanner(); + $this->cache = $storage->getCache(); + } + + /** + * Disable updating the cache trough this updater + */ + public function disable() { + $this->enabled = false; + } + + /** + * Re-enable the updating of the cache trough this updater + */ + public function enable() { + $this->enabled = true; + } + + /** + * Get the propagator for etags and mtime for the view the updater works on + * + * @return Propagator + */ + public function getPropagator() { + return $this->propagator; + } + + /** + * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem + * + * @param string $path the path of the file to propagate the changes for + * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used + */ + public function propagate($path, $time = null) { + if (Scanner::isPartialFile($path)) { + return; + } + $this->propagator->propagateChange($path, $time); + } + + /** + * Update the cache for $path and update the size, etag and mtime of the parent folders + * + * @param string $path + * @param int $time + */ + public function update($path, $time = null) { + if (!$this->enabled or Scanner::isPartialFile($path)) { + return; + } + if (is_null($time)) { + $time = time(); + } + + $data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false); + if ( + isset($data['oldSize']) && isset($data['size']) && + !$data['encrypted'] // encryption is a pita and touches the cache itself + ) { + $sizeDifference = $data['size'] - $data['oldSize']; + } else { + // scanner didn't provide size info, fallback to full size calculation + $sizeDifference = 0; + if ($this->cache instanceof Cache) { + $this->cache->correctFolderSize($path, $data); + } + } + $this->correctParentStorageMtime($path); + $this->propagator->propagateChange($path, $time, $sizeDifference); + } + + /** + * Remove $path from the cache and update the size, etag and mtime of the parent folders + * + * @param string $path + */ + public function remove($path) { + if (!$this->enabled or Scanner::isPartialFile($path)) { + return; + } + + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + + $entry = $this->cache->get($path); + + $this->cache->remove($path); + + $this->correctParentStorageMtime($path); + if ($entry instanceof ICacheEntry) { + $this->propagator->propagateChange($path, time(), -$entry->getSize()); + } else { + $this->propagator->propagateChange($path, time()); + if ($this->cache instanceof Cache) { + $this->cache->correctFolderSize($parent); + } + } + } + + /** + * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders + * + * @param IStorage $sourceStorage + * @param string $source + * @param string $target + */ + public function renameFromStorage(IStorage $sourceStorage, $source, $target) { + if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) { + return; + } + + $time = time(); + + $sourceCache = $sourceStorage->getCache(); + $sourceUpdater = $sourceStorage->getUpdater(); + $sourcePropagator = $sourceStorage->getPropagator(); + + $sourceInfo = $sourceCache->get($source); + + if ($sourceInfo !== false) { + if ($this->cache->inCache($target)) { + $this->cache->remove($target); + } + + if ($sourceStorage === $this->storage) { + $this->cache->move($source, $target); + } else { + $this->cache->moveFromCache($sourceCache, $source, $target); + } + + $sourceExtension = pathinfo($source, PATHINFO_EXTENSION); + $targetExtension = pathinfo($target, PATHINFO_EXTENSION); + $targetIsTrash = preg_match("/d\d+/", $targetExtension); + + if ($sourceExtension !== $targetExtension && $sourceInfo->getMimeType() !== FileInfo::MIMETYPE_FOLDER && !$targetIsTrash) { + // handle mime type change + $mimeType = $this->storage->getMimeType($target); + $fileId = $this->cache->getId($target); + $this->cache->update($fileId, ['mimetype' => $mimeType]); + } + } + + if ($sourceCache instanceof Cache) { + $sourceCache->correctFolderSize($source); + } + if ($this->cache instanceof Cache) { + $this->cache->correctFolderSize($target); + } + if ($sourceUpdater instanceof Updater) { + $sourceUpdater->correctParentStorageMtime($source); + } + $this->correctParentStorageMtime($target); + $this->updateStorageMTimeOnly($target); + $sourcePropagator->propagateChange($source, $time); + $this->propagator->propagateChange($target, $time); + } + + private function updateStorageMTimeOnly($internalPath) { + $fileId = $this->cache->getId($internalPath); + if ($fileId !== -1) { + $mtime = $this->storage->filemtime($internalPath); + if ($mtime !== false) { + $this->cache->update( + $fileId, [ + 'mtime' => null, // this magic tells it to not overwrite mtime + 'storage_mtime' => $mtime + ] + ); + } + } + } + + /** + * update the storage_mtime of the direct parent in the cache to the mtime from the storage + * + * @param string $internalPath + */ + private function correctParentStorageMtime($internalPath) { + $parentId = $this->cache->getParentId($internalPath); + $parent = dirname($internalPath); + if ($parentId != -1) { + $mtime = $this->storage->filemtime($parent); + if ($mtime !== false) { + $this->cache->update($parentId, ['storage_mtime' => $mtime]); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Watcher.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Watcher.php new file mode 100644 index 0000000..d6291ca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Watcher.php @@ -0,0 +1,146 @@ + + * @author Daniel Jagszent + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\IWatcher; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Watcher implements IWatcher { + protected $watchPolicy = self::CHECK_ONCE; + + protected $checkedPaths = []; + + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + /** + * @var Cache $cache + */ + protected $cache; + + /** + * @var Scanner $scanner ; + */ + protected $scanner; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = $storage->getCache(); + $this->scanner = $storage->getScanner(); + } + + /** + * @param int $policy either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS + */ + public function setPolicy($policy) { + $this->watchPolicy = $policy; + } + + /** + * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS + */ + public function getPolicy() { + return $this->watchPolicy; + } + + /** + * check $path for updates and update if needed + * + * @param string $path + * @param ICacheEntry|null $cachedEntry + * @return boolean true if path was updated + */ + public function checkUpdate($path, $cachedEntry = null) { + if (is_null($cachedEntry)) { + $cachedEntry = $this->cache->get($path); + } + if ($cachedEntry === false || $this->needsUpdate($path, $cachedEntry)) { + $this->update($path, $cachedEntry); + return true; + } else { + return false; + } + } + + /** + * Update the cache for changes to $path + * + * @param string $path + * @param ICacheEntry $cachedData + */ + public function update($path, $cachedData) { + if ($this->storage->is_dir($path)) { + $this->scanner->scan($path, Scanner::SCAN_SHALLOW); + } else { + $this->scanner->scanFile($path); + } + if (is_array($cachedData) && $cachedData['mimetype'] === 'httpd/unix-directory') { + $this->cleanFolder($path); + } + if ($this->cache instanceof Cache) { + $this->cache->correctFolderSize($path); + } + } + + /** + * Check if the cache for $path needs to be updated + * + * @param string $path + * @param ICacheEntry $cachedData + * @return bool + */ + public function needsUpdate($path, $cachedData) { + if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) { + $this->checkedPaths[] = $path; + return $this->storage->hasUpdated($path, $cachedData['storage_mtime']); + } + return false; + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + $cachedContent = $this->cache->getFolderContents($path); + foreach ($cachedContent as $entry) { + if (!$this->storage->file_exists($entry['path'])) { + $this->cache->remove($entry['path']); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheJail.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheJail.php new file mode 100644 index 0000000..6c1c17b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -0,0 +1,331 @@ + + * @author Daniel Jagszent + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Cache; +use OC\Files\Search\SearchQuery; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchQuery; + +/** + * Jail to a subdirectory of the wrapped cache + */ +class CacheJail extends CacheWrapper { + /** + * @var string + */ + protected $root; + + /** + * @param \OCP\Files\Cache\ICache $cache + * @param string $root + */ + public function __construct($cache, $root) { + parent::__construct($cache); + $this->root = $root; + } + + protected function getRoot() { + return $this->root; + } + + protected function getSourcePath($path) { + if ($path === '') { + return $this->getRoot(); + } else { + return $this->getRoot() . '/' . ltrim($path, '/'); + } + } + + /** + * @param string $path + * @return null|string the jailed path or null if the path is outside the jail + */ + protected function getJailedPath($path) { + if ($this->getRoot() === '') { + return $path; + } + $rootLength = strlen($this->getRoot()) + 1; + if ($path === $this->getRoot()) { + return ''; + } elseif (substr($path, 0, $rootLength) === $this->getRoot() . '/') { + return substr($path, $rootLength); + } else { + return null; + } + } + + /** + * @param ICacheEntry|array $entry + * @return array + */ + protected function formatCacheEntry($entry) { + if (isset($entry['path'])) { + $entry['path'] = $this->getJailedPath($entry['path']); + } + return $entry; + } + + protected function filterCacheEntry($entry) { + $rootLength = strlen($this->getRoot()) + 1; + return $rootLength === 1 || ($entry['path'] === $this->getRoot()) || (substr($entry['path'], 0, $rootLength) === $this->getRoot() . '/'); + } + + /** + * get the stored metadata of a file or folder + * + * @param string /int $file + * @return ICacheEntry|false + */ + public function get($file) { + if (is_string($file) or $file == '') { + $file = $this->getSourcePath($file); + } + return parent::get($file); + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->getCache()->insert($this->getSourcePath($file), $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->getCache()->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->getCache()->getId($this->getSourcePath($file)); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + return $this->getCache()->getParentId($this->getSourcePath($file)); + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getCache()->inCache($this->getSourcePath($file)); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->getCache()->remove($this->getSourcePath($file)); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->getCache()->move($this->getSourcePath($source), $this->getSourcePath($target)); + } + + /** + * Get the storage id and path needed for a move + * + * @param string $path + * @return array [$storageId, $internalPath] + */ + protected function getMoveInfo($path) { + return [$this->getNumericStorageId(), $this->getSourcePath($path)]; + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->getCache()->remove($this->getRoot()); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->getCache()->getStatus($this->getSourcePath($file)); + } + + private function formatSearchResults($results) { + $results = array_filter($results, [$this, 'filterCacheEntry']); + $results = array_values($results); + return array_map([$this, 'formatCacheEntry'], $results); + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array an array of file data + */ + public function search($pattern) { + $results = $this->getCache()->search($pattern); + return $this->formatSearchResults($results); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return array + */ + public function searchByMime($mimetype) { + $results = $this->getCache()->searchByMime($mimetype); + return $this->formatSearchResults($results); + } + + public function searchQuery(ISearchQuery $query) { + $simpleQuery = new SearchQuery($query->getSearchOperation(), 0, 0, $query->getOrder(), $query->getUser()); + $results = $this->getCache()->searchQuery($simpleQuery); + $results = $this->formatSearchResults($results); + + $limit = $query->getLimit() === 0 ? null : $query->getLimit(); + $results = array_slice($results, $query->getOffset(), $limit); + + return $results; + } + + /** + * update the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { + if ($this->getCache() instanceof Cache) { + $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + if ($this->getCache() instanceof Cache) { + return $this->getCache()->calculateFolderSize($this->getSourcePath($path), $entry); + } else { + return 0; + } + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + // not supported + return []; + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + // not supported + return false; + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + $path = $this->getCache()->getPathById($id); + if ($path === null) { + return null; + } + + return $this->getJailedPath($path); + } + + /** + * Move a file or folder in the cache + * + * Note that this should make sure the entries are removed from the source cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + */ + public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) { + if ($sourceCache === $this) { + return $this->move($sourcePath, $targetPath); + } + return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php new file mode 100644 index 0000000..706f5d8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php @@ -0,0 +1,48 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +class CachePermissionsMask extends CacheWrapper { + /** + * @var int + */ + protected $mask; + + /** + * @param \OCP\Files\Cache\ICache $cache + * @param int $mask + */ + public function __construct($cache, $mask) { + parent::__construct($cache); + $this->mask = $mask; + } + + protected function formatCacheEntry($entry) { + if (isset($entry['permissions'])) { + $entry['scan_permissions'] = $entry['permissions']; + $entry['permissions'] &= $this->mask; + } + return $entry; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheWrapper.php new file mode 100644 index 0000000..4901a53 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -0,0 +1,325 @@ + + * @author Christoph Wurst + * @author Daniel Jagszent + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Cache; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchQuery; + +class CacheWrapper extends Cache { + /** + * @var \OCP\Files\Cache\ICache + */ + protected $cache; + + /** + * @param \OCP\Files\Cache\ICache $cache + */ + public function __construct($cache) { + $this->cache = $cache; + } + + protected function getCache() { + return $this->cache; + } + + /** + * Make it easy for wrappers to modify every returned cache entry + * + * @param ICacheEntry $entry + * @return ICacheEntry + */ + protected function formatCacheEntry($entry) { + return $entry; + } + + /** + * get the stored metadata of a file or folder + * + * @param string|int $file + * @return ICacheEntry|false + */ + public function get($file) { + $result = $this->getCache()->get($file); + if ($result) { + $result = $this->formatCacheEntry($result); + } + return $result; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + */ + public function getFolderContents($folder) { + // can't do a simple $this->getCache()->.... call here since getFolderContentsById needs to be called on this + // and not the wrapped cache + $fileId = $this->getId($folder); + return $this->getFolderContentsById($fileId); + } + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return array + */ + public function getFolderContentsById($fileId) { + $results = $this->getCache()->getFolderContentsById($fileId); + return array_map([$this, 'formatCacheEntry'], $results); + } + + /** + * insert or update meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + return $this->insert($file, $data); + } + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->getCache()->insert($file, $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->getCache()->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->getCache()->getId($file); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + return $this->getCache()->getParentId($file); + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getCache()->inCache($file); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->getCache()->remove($file); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->getCache()->move($source, $target); + } + + protected function getMoveInfo($path) { + /** @var Cache $cache */ + $cache = $this->getCache(); + return $cache->getMoveInfo($path); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + $this->getCache()->moveFromCache($sourceCache, $sourcePath, $targetPath); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->getCache()->clear(); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->getCache()->getStatus($file); + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return ICacheEntry[] an array of file data + */ + public function search($pattern) { + $results = $this->getCache()->search($pattern); + return array_map([$this, 'formatCacheEntry'], $results); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return ICacheEntry[] + */ + public function searchByMime($mimetype) { + $results = $this->getCache()->searchByMime($mimetype); + return array_map([$this, 'formatCacheEntry'], $results); + } + + public function searchQuery(ISearchQuery $query) { + $results = $this->getCache()->searchQuery($query); + return array_map([$this, 'formatCacheEntry'], $results); + } + + /** + * update the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { + if ($this->getCache() instanceof Cache) { + $this->getCache()->correctFolderSize($path, $data, $isBackgroundScan); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + if ($this->getCache() instanceof Cache) { + return $this->getCache()->calculateFolderSize($path, $entry); + } else { + return 0; + } + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + return $this->getCache()->getAll(); + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + return $this->getCache()->getIncomplete(); + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + return $this->getCache()->getPathById($id); + } + + /** + * Returns the numeric storage id + * + * @return int + */ + public function getNumericStorageId() { + return $this->getCache()->getNumericStorageId(); + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * unlike getPathById this does not limit the search to files on this storage and + * instead does a global search in the cache table + * + * @param int $id + * @return array first element holding the storage id, second the path + */ + public static function getById($id) { + return parent::getById($id); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/JailPropagator.php b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/JailPropagator.php new file mode 100644 index 0000000..956d187 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Cache/Wrapper/JailPropagator.php @@ -0,0 +1,46 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Propagator; +use OC\Files\Storage\Wrapper\Jail; + +class JailPropagator extends Propagator { + /** + * @var Jail + */ + protected $storage; + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $sourceInternalPath) = $this->storage->resolvePath($internalPath); + $storage->getPropagator()->propagateChange($sourceInternalPath, $time, $sizeDifference); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountFileInfo.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountFileInfo.php new file mode 100644 index 0000000..7bae52b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountFileInfo.php @@ -0,0 +1,50 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Config; + +use OCP\Files\Config\ICachedMountFileInfo; +use OCP\IUser; + +class CachedMountFileInfo extends CachedMountInfo implements ICachedMountFileInfo { + /** @var string */ + private $internalPath; + + public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId = null, $rootInternalPath = '', $internalPath) { + parent::__construct($user, $storageId, $rootId, $mountPoint, $mountId, $rootInternalPath); + $this->internalPath = $internalPath; + } + + public function getInternalPath() { + if ($this->getRootInternalPath()) { + return substr($this->internalPath, strlen($this->getRootInternalPath()) + 1); + } else { + return $this->internalPath; + } + } + + public function getPath() { + return $this->getMountPoint() . $this->getInternalPath(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountInfo.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountInfo.php new file mode 100644 index 0000000..3891902 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/CachedMountInfo.php @@ -0,0 +1,142 @@ + + * @author Semih Serhat Karakaya + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Files\Filesystem; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Node; +use OCP\IUser; + +class CachedMountInfo implements ICachedMountInfo { + /** + * @var IUser + */ + protected $user; + + /** + * @var int + */ + protected $storageId; + + /** + * @var int + */ + protected $rootId; + + /** + * @var string + */ + protected $mountPoint; + + /** + * @var int|null + */ + protected $mountId; + + /** + * @var string + */ + protected $rootInternalPath; + + /** + * CachedMountInfo constructor. + * + * @param IUser $user + * @param int $storageId + * @param int $rootId + * @param string $mountPoint + * @param int|null $mountId + * @param string $rootInternalPath + */ + public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId = null, $rootInternalPath = '') { + $this->user = $user; + $this->storageId = $storageId; + $this->rootId = $rootId; + $this->mountPoint = $mountPoint; + $this->mountId = $mountId; + $this->rootInternalPath = $rootInternalPath; + } + + /** + * @return IUser + */ + public function getUser() { + return $this->user; + } + + /** + * @return int the numeric storage id of the mount + */ + public function getStorageId() { + return $this->storageId; + } + + /** + * @return int the fileid of the root of the mount + */ + public function getRootId() { + return $this->rootId; + } + + /** + * @return Node the root node of the mount + */ + public function getMountPointNode() { + // TODO injection etc + Filesystem::initMountPoints($this->getUser()->getUID()); + $userNode = \OC::$server->getUserFolder($this->getUser()->getUID()); + $nodes = $userNode->getParent()->getById($this->getRootId()); + if (count($nodes) > 0) { + return $nodes[0]; + } else { + return null; + } + } + + /** + * @return string the mount point of the mount for the user + */ + public function getMountPoint() { + return $this->mountPoint; + } + + /** + * Get the id of the configured mount + * + * @return int|null mount id or null if not applicable + * @since 9.1.0 + */ + public function getMountId() { + return $this->mountId; + } + + /** + * Get the internal path (within the storage) of the root of the mount + * + * @return string + */ + public function getRootInternalPath() { + return $this->rootInternalPath; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/LazyStorageMountInfo.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/LazyStorageMountInfo.php new file mode 100644 index 0000000..8d183d4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/LazyStorageMountInfo.php @@ -0,0 +1,85 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OCP\Files\Mount\IMountPoint; +use OCP\IUser; + +class LazyStorageMountInfo extends CachedMountInfo { + /** @var IMountPoint */ + private $mount; + + /** + * CachedMountInfo constructor. + * + * @param IUser $user + * @param IMountPoint $mount + */ + public function __construct(IUser $user, IMountPoint $mount) { + $this->user = $user; + $this->mount = $mount; + } + + /** + * @return int the numeric storage id of the mount + */ + public function getStorageId() { + if (!$this->storageId) { + $this->storageId = $this->mount->getNumericStorageId(); + } + return parent::getStorageId(); + } + + /** + * @return int the fileid of the root of the mount + */ + public function getRootId() { + if (!$this->rootId) { + $this->rootId = $this->mount->getStorageRootId(); + } + return parent::getRootId(); + } + + /** + * @return string the mount point of the mount for the user + */ + public function getMountPoint() { + if (!$this->mountPoint) { + $this->mountPoint = $this->mount->getMountPoint(); + } + return parent::getMountPoint(); + } + + public function getMountId() { + return $this->mount->getMountId(); + } + + /** + * Get the internal path (within the storage) of the root of the mount + * + * @return string + */ + public function getRootInternalPath() { + return $this->mount->getInternalPath($this->mount->getMountPoint()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/MountProviderCollection.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/MountProviderCollection.php new file mode 100644 index 0000000..3900e98 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/MountProviderCollection.php @@ -0,0 +1,226 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Hooks\Emitter; +use OC\Hooks\EmitterTrait; +use OCP\Files\Config\IHomeMountProvider; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Config\IRootMountProvider; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +class MountProviderCollection implements IMountProviderCollection, Emitter { + use EmitterTrait; + + /** + * @var \OCP\Files\Config\IHomeMountProvider[] + */ + private $homeProviders = []; + + /** + * @var \OCP\Files\Config\IMountProvider[] + */ + private $providers = []; + + /** @var \OCP\Files\Config\IRootMountProvider[] */ + private $rootProviders = []; + + /** + * @var \OCP\Files\Storage\IStorageFactory + */ + private $loader; + + /** + * @var \OCP\Files\Config\IUserMountCache + */ + private $mountCache; + + /** @var callable[] */ + private $mountFilters = []; + + /** + * @param \OCP\Files\Storage\IStorageFactory $loader + * @param IUserMountCache $mountCache + */ + public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) { + $this->loader = $loader; + $this->mountCache = $mountCache; + } + + /** + * Get all configured mount points for the user + * + * @param \OCP\IUser $user + * @return \OCP\Files\Mount\IMountPoint[] + */ + public function getMountsForUser(IUser $user) { + $loader = $this->loader; + $mounts = array_map(function (IMountProvider $provider) use ($user, $loader) { + return $provider->getMountsForUser($user, $loader); + }, $this->providers); + $mounts = array_filter($mounts, function ($result) { + return is_array($result); + }); + $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) { + return array_merge($mounts, $providerMounts); + }, []); + return $this->filterMounts($user, $mounts); + } + + public function addMountForUser(IUser $user, IMountManager $mountManager) { + // shared mount provider gets to go last since it needs to know existing files + // to check for name collisions + $firstMounts = []; + $firstProviders = array_filter($this->providers, function (IMountProvider $provider) { + return (get_class($provider) !== 'OCA\Files_Sharing\MountProvider'); + }); + $lastProviders = array_filter($this->providers, function (IMountProvider $provider) { + return (get_class($provider) === 'OCA\Files_Sharing\MountProvider'); + }); + foreach ($firstProviders as $provider) { + $mounts = $provider->getMountsForUser($user, $this->loader); + if (is_array($mounts)) { + $firstMounts = array_merge($firstMounts, $mounts); + } + } + $firstMounts = $this->filterMounts($user, $firstMounts); + array_walk($firstMounts, [$mountManager, 'addMount']); + + $lateMounts = []; + foreach ($lastProviders as $provider) { + $mounts = $provider->getMountsForUser($user, $this->loader); + if (is_array($mounts)) { + $lateMounts = array_merge($lateMounts, $mounts); + } + } + + $lateMounts = $this->filterMounts($user, $lateMounts); + array_walk($lateMounts, [$mountManager, 'addMount']); + + return array_merge($lateMounts, $firstMounts); + } + + /** + * Get the configured home mount for this user + * + * @param \OCP\IUser $user + * @return \OCP\Files\Mount\IMountPoint + * @since 9.1.0 + */ + public function getHomeMountForUser(IUser $user) { + /** @var \OCP\Files\Config\IHomeMountProvider[] $providers */ + $providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin + foreach ($providers as $homeProvider) { + if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) { + $mount->setMountPoint('/' . $user->getUID()); //make sure the mountpoint is what we expect + return $mount; + } + } + throw new \Exception('No home storage configured for user ' . $user); + } + + /** + * Add a provider for mount points + * + * @param \OCP\Files\Config\IMountProvider $provider + */ + public function registerProvider(IMountProvider $provider) { + $this->providers[] = $provider; + + $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]); + } + + public function registerMountFilter(callable $filter) { + $this->mountFilters[] = $filter; + } + + private function filterMounts(IUser $user, array $mountPoints) { + return array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) { + foreach ($this->mountFilters as $filter) { + if ($filter($mountPoint, $user) === false) { + return false; + } + } + return true; + }); + } + + /** + * Add a provider for home mount points + * + * @param \OCP\Files\Config\IHomeMountProvider $provider + * @since 9.1.0 + */ + public function registerHomeProvider(IHomeMountProvider $provider) { + $this->homeProviders[] = $provider; + $this->emit('\OC\Files\Config', 'registerHomeMountProvider', [$provider]); + } + + /** + * Cache mounts for user + * + * @param IUser $user + * @param IMountPoint[] $mountPoints + */ + public function registerMounts(IUser $user, array $mountPoints) { + $this->mountCache->registerMounts($user, $mountPoints); + } + + /** + * Get the mount cache which can be used to search for mounts without setting up the filesystem + * + * @return IUserMountCache + */ + public function getMountCache() { + return $this->mountCache; + } + + public function registerRootProvider(IRootMountProvider $provider) { + $this->rootProviders[] = $provider; + } + + /** + * Get all root mountpoints + * + * @return \OCP\Files\Mount\IMountPoint[] + * @since 20.0.0 + */ + public function getRootMounts(): array { + $loader = $this->loader; + $mounts = array_map(function (IRootMountProvider $provider) use ($loader) { + return $provider->getRootMounts($loader); + }, $this->rootProviders); + $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) { + return array_merge($mounts, $providerMounts); + }, []); + return $mounts; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCache.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCache.php new file mode 100644 index 0000000..6fa9cd9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCache.php @@ -0,0 +1,417 @@ + + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Cache\CappedMemoryCache; +use OCA\Files_Sharing\SharedMount; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Config\ICachedMountFileInfo; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; +use OCP\ICache; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; + +/** + * Cache mounts points per user in the cache so we can easilly look them up + */ +class UserMountCache implements IUserMountCache { + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUserManager + */ + private $userManager; + + /** + * Cached mount info. + * Map of $userId to ICachedMountInfo. + * + * @var ICache + **/ + private $mountsForUsers; + + /** + * @var ILogger + */ + private $logger; + + /** + * @var ICache + */ + private $cacheInfoCache; + + /** + * UserMountCache constructor. + * + * @param IDBConnection $connection + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { + $this->connection = $connection; + $this->userManager = $userManager; + $this->logger = $logger; + $this->cacheInfoCache = new CappedMemoryCache(); + $this->mountsForUsers = new CappedMemoryCache(); + } + + public function registerMounts(IUser $user, array $mounts) { + // filter out non-proper storages coming from unit tests + $mounts = array_filter($mounts, function (IMountPoint $mount) { + return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache(); + }); + /** @var ICachedMountInfo[] $newMounts */ + $newMounts = array_map(function (IMountPoint $mount) use ($user) { + // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) + if ($mount->getStorageRootId() === -1) { + return null; + } else { + return new LazyStorageMountInfo($user, $mount); + } + }, $mounts); + $newMounts = array_values(array_filter($newMounts)); + $newMountRootIds = array_map(function (ICachedMountInfo $mount) { + return $mount->getRootId(); + }, $newMounts); + $newMounts = array_combine($newMountRootIds, $newMounts); + + $cachedMounts = $this->getMountsForUser($user); + $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) { + return $mount->getRootId(); + }, $cachedMounts); + $cachedMounts = array_combine($cachedMountRootIds, $cachedMounts); + + $addedMounts = []; + $removedMounts = []; + + foreach ($newMounts as $rootId => $newMount) { + if (!isset($cachedMounts[$rootId])) { + $addedMounts[] = $newMount; + } + } + + foreach ($cachedMounts as $rootId => $cachedMount) { + if (!isset($newMounts[$rootId])) { + $removedMounts[] = $cachedMount; + } + } + + $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts); + + foreach ($addedMounts as $mount) { + $this->addToCache($mount); + $this->mountsForUsers[$user->getUID()][] = $mount; + } + foreach ($removedMounts as $mount) { + $this->removeFromCache($mount); + $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); + unset($this->mountsForUsers[$user->getUID()][$index]); + } + foreach ($changedMounts as $mount) { + $this->updateCachedMount($mount); + } + } + + /** + * @param ICachedMountInfo[] $newMounts + * @param ICachedMountInfo[] $cachedMounts + * @return ICachedMountInfo[] + */ + private function findChangedMounts(array $newMounts, array $cachedMounts) { + $new = []; + foreach ($newMounts as $mount) { + $new[$mount->getRootId()] = $mount; + } + $changed = []; + foreach ($cachedMounts as $cachedMount) { + $rootId = $cachedMount->getRootId(); + if (isset($new[$rootId])) { + $newMount = $new[$rootId]; + if ( + $newMount->getMountPoint() !== $cachedMount->getMountPoint() || + $newMount->getStorageId() !== $cachedMount->getStorageId() || + $newMount->getMountId() !== $cachedMount->getMountId() + ) { + $changed[] = $newMount; + } + } + } + return $changed; + } + + private function addToCache(ICachedMountInfo $mount) { + if ($mount->getStorageId() !== -1) { + $this->connection->insertIfNotExist('*PREFIX*mounts', [ + 'storage_id' => $mount->getStorageId(), + 'root_id' => $mount->getRootId(), + 'user_id' => $mount->getUser()->getUID(), + 'mount_point' => $mount->getMountPoint(), + 'mount_id' => $mount->getMountId() + ], ['root_id', 'user_id']); + } else { + // in some cases this is legitimate, like orphaned shares + $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint()); + } + } + + private function updateCachedMount(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('mounts') + ->set('storage_id', $builder->createNamedParameter($mount->getStorageId())) + ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) + ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT)) + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); + + $query->execute(); + } + + private function removeFromCache(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + private function dbRowToMountInfo(array $row) { + $user = $this->userManager->get($row['user_id']); + if (is_null($user)) { + return null; + } + $mount_id = $row['mount_id']; + if (!is_null($mount_id)) { + $mount_id = (int)$mount_id; + } + return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : ''); + } + + /** + * @param IUser $user + * @return ICachedMountInfo[] + */ + public function getMountsForUser(IUser $user) { + if (!isset($this->mountsForUsers[$user->getUID()])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) + ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); + + $rows = $query->execute()->fetchAll(); + + $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); + } + return $this->mountsForUsers[$user->getUID()]; + } + + /** + * @param int $numericStorageId + * @param string|null $user limit the results to a single user + * @return CachedMountInfo[] + */ + public function getMountsForStorageId($numericStorageId, $user = null) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) + ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); + + if ($user) { + $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user))); + } + + $rows = $query->execute()->fetchAll(); + + return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); + } + + /** + * @param int $rootFileId + * @return CachedMountInfo[] + */ + public function getMountsForRootId($rootFileId) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) + ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); + + $rows = $query->execute()->fetchAll(); + + return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); + } + + /** + * @param $fileId + * @return array + * @throws \OCP\Files\NotFoundException + */ + private function getCacheInfoFromFileId($fileId) { + if (!isset($this->cacheInfoCache[$fileId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage', 'path', 'mimetype') + ->from('filecache') + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + + $row = $query->execute()->fetch(); + if (is_array($row)) { + $this->cacheInfoCache[$fileId] = [ + (int)$row['storage'], + $row['path'], + (int)$row['mimetype'] + ]; + } else { + throw new NotFoundException('File with id "' . $fileId . '" not found'); + } + } + return $this->cacheInfoCache[$fileId]; + } + + /** + * @param int $fileId + * @param string|null $user optionally restrict the results to a single user + * @return ICachedMountFileInfo[] + * @since 9.0.0 + */ + public function getMountsForFileId($fileId, $user = null) { + try { + list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId); + } catch (NotFoundException $e) { + return []; + } + $mountsForStorage = $this->getMountsForStorageId($storageId, $user); + + // filter mounts that are from the same storage but a different directory + $filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) { + if ($fileId === $mount->getRootId()) { + return true; + } + $internalMountPath = $mount->getRootInternalPath(); + + return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; + }); + + return array_map(function (ICachedMountInfo $mount) use ($internalPath) { + return new CachedMountFileInfo( + $mount->getUser(), + $mount->getStorageId(), + $mount->getRootId(), + $mount->getMountPoint(), + $mount->getMountId(), + $mount->getRootInternalPath(), + $internalPath + ); + }, $filteredMounts); + } + + /** + * Remove all cached mounts for a user + * + * @param IUser $user + */ + public function removeUserMounts(IUser $user) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID()))); + $query->execute(); + } + + public function removeUserStorageMount($storageId, $userId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId))) + ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + public function remoteStorageMounts($storageId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + /** + * @param array $users + * @return array + */ + public function getUsedSpaceForUsers(array $users) { + $builder = $this->connection->getQueryBuilder(); + + $slash = $builder->createNamedParameter('/'); + + $mountPoint = $builder->func()->concat( + $builder->func()->concat($slash, 'user_id'), + $slash + ); + + $userIds = array_map(function (IUser $user) { + return $user->getUID(); + }, $users); + + $query = $builder->select('m.user_id', 'f.size') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', + $builder->expr()->andX( + $builder->expr()->eq('m.storage_id', 'f.storage'), + $builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files'))) + )) + ->where($builder->expr()->eq('m.mount_point', $mountPoint)) + ->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY))); + + $result = $query->execute(); + + $results = []; + while ($row = $result->fetch()) { + $results[$row['user_id']] = $row['size']; + } + $result->closeCursor(); + return $results; + } + + public function clear(): void { + $this->cacheInfoCache = new CappedMemoryCache(); + $this->mountsForUsers = new CappedMemoryCache(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCacheListener.php b/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCacheListener.php new file mode 100644 index 0000000..bacd572 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Config/UserMountCacheListener.php @@ -0,0 +1,49 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\User\Manager; +use OCP\Files\Config\IUserMountCache; + +/** + * Listen to hooks and update the mount cache as needed + */ +class UserMountCacheListener { + /** + * @var IUserMountCache + */ + private $userMountCache; + + /** + * UserMountCacheListener constructor. + * + * @param IUserMountCache $userMountCache + */ + public function __construct(IUserMountCache $userMountCache) { + $this->userMountCache = $userMountCache; + } + + public function listen(Manager $manager) { + $manager->listen('\OC\User', 'postDelete', [$this->userMountCache, 'removeUserMounts']); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/FileInfo.php b/docker/overlays/nextcloud/html/lib/private/Files/FileInfo.php new file mode 100644 index 0000000..3c6bdd3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/FileInfo.php @@ -0,0 +1,417 @@ + + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Piotr M + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author tbartenstein + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files; + +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Mount\IMountPoint; +use OCP\IUser; + +class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { + /** + * @var array $data + */ + private $data; + + /** + * @var string $path + */ + private $path; + + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var string $internalPath + */ + private $internalPath; + + /** + * @var \OCP\Files\Mount\IMountPoint + */ + private $mount; + + /** + * @var IUser + */ + private $owner; + + /** + * @var string[] + */ + private $childEtags = []; + + /** + * @var IMountPoint[] + */ + private $subMounts = []; + + private $subMountsUsed = false; + + /** + * The size of the file/folder without any sub mount + * + * @var int + */ + private $rawSize = 0; + + /** + * @param string|boolean $path + * @param Storage\Storage $storage + * @param string $internalPath + * @param array|ICacheEntry $data + * @param \OCP\Files\Mount\IMountPoint $mount + * @param \OCP\IUser|null $owner + */ + public function __construct($path, $storage, $internalPath, $data, $mount, $owner= null) { + $this->path = $path; + $this->storage = $storage; + $this->internalPath = $internalPath; + $this->data = $data; + $this->mount = $mount; + $this->owner = $owner; + $this->rawSize = $this->data['size'] ?? 0; + } + + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + public function offsetGet($offset) { + if ($offset === 'type') { + return $this->getType(); + } elseif ($offset === 'etag') { + return $this->getEtag(); + } elseif ($offset === 'size') { + return $this->getSize(); + } elseif ($offset === 'mtime') { + return $this->getMTime(); + } elseif ($offset === 'permissions') { + return $this->getPermissions(); + } elseif (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return \OCP\Files\Storage + */ + public function getStorage() { + return $this->storage; + } + + /** + * @return string + */ + public function getInternalPath() { + return $this->internalPath; + } + + /** + * Get FileInfo ID or null in case of part file + * + * @return int|null + */ + public function getId() { + return isset($this->data['fileid']) ? (int) $this->data['fileid'] : null; + } + + /** + * @return string + */ + public function getMimetype() { + return $this->data['mimetype']; + } + + /** + * @return string + */ + public function getMimePart() { + return $this->data['mimepart']; + } + + /** + * @return string + */ + public function getName() { + return isset($this->data['name']) ? $this->data['name'] : basename($this->getPath()); + } + + /** + * @return string + */ + public function getEtag() { + $this->updateEntryfromSubMounts(); + if (count($this->childEtags) > 0) { + $combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags); + return md5($combinedEtag); + } else { + return $this->data['etag']; + } + } + + /** + * @return int + */ + public function getSize($includeMounts = true) { + if ($includeMounts) { + $this->updateEntryfromSubMounts(); + return isset($this->data['size']) ? 0 + $this->data['size'] : 0; + } else { + return $this->rawSize; + } + } + + /** + * @return int + */ + public function getMTime() { + $this->updateEntryfromSubMounts(); + return (int) $this->data['mtime']; + } + + /** + * @return bool + */ + public function isEncrypted() { + return $this->data['encrypted']; + } + + /** + * Return the currently version used for the HMAC in the encryption app + * + * @return int + */ + public function getEncryptedVersion() { + return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1; + } + + /** + * @return int + */ + public function getPermissions() { + $perms = (int) $this->data['permissions']; + if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) { + $perms = $perms & ~\OCP\Constants::PERMISSION_SHARE; + } + return (int) $perms; + } + + /** + * @return string \OCP\Files\FileInfo::TYPE_FILE|\OCP\Files\FileInfo::TYPE_FOLDER + */ + public function getType() { + if (!isset($this->data['type'])) { + $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE; + } + return $this->data['type']; + } + + public function getData() { + return $this->data; + } + + /** + * @param int $permissions + * @return bool + */ + protected function checkPermissions($permissions) { + return ($this->getPermissions() & $permissions) === $permissions; + } + + /** + * @return bool + */ + public function isReadable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_READ); + } + + /** + * @return bool + */ + public function isUpdateable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE); + } + + /** + * Check whether new files or folders can be created inside this folder + * + * @return bool + */ + public function isCreatable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_CREATE); + } + + /** + * @return bool + */ + public function isDeletable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_DELETE); + } + + /** + * @return bool + */ + public function isShareable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_SHARE); + } + + /** + * Check if a file or folder is shared + * + * @return bool + */ + public function isShared() { + $sid = $this->getStorage()->getId(); + if (!is_null($sid)) { + $sid = explode(':', $sid); + return ($sid[0] === 'shared'); + } + + return false; + } + + public function isMounted() { + $storage = $this->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) { + return false; + } + $sid = $storage->getId(); + if (!is_null($sid)) { + $sid = explode(':', $sid); + return ($sid[0] !== 'home' and $sid[0] !== 'shared'); + } + + return false; + } + + /** + * Get the mountpoint the file belongs to + * + * @return \OCP\Files\Mount\IMountPoint + */ + public function getMountPoint() { + return $this->mount; + } + + /** + * Get the owner of the file + * + * @return \OCP\IUser + */ + public function getOwner() { + return $this->owner; + } + + /** + * @param IMountPoint[] $mounts + */ + public function setSubMounts(array $mounts) { + $this->subMounts = $mounts; + } + + private function updateEntryfromSubMounts() { + if ($this->subMountsUsed) { + return; + } + $this->subMountsUsed = true; + foreach ($this->subMounts as $mount) { + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + $rootEntry = $subCache->get(''); + $this->addSubEntry($rootEntry, $mount->getMountPoint()); + } + } + } + + /** + * Add a cache entry which is the child of this folder + * + * Sets the size, etag and size to for cross-storage childs + * + * @param array|ICacheEntry $data cache entry for the child + * @param string $entryPath full path of the child entry + */ + public function addSubEntry($data, $entryPath) { + $this->data['size'] += isset($data['size']) ? $data['size'] : 0; + if (isset($data['mtime'])) { + $this->data['mtime'] = max($this->data['mtime'], $data['mtime']); + } + if (isset($data['etag'])) { + // prefix the etag with the relative path of the subentry to propagate etag on mount moves + $relativeEntryPath = substr($entryPath, strlen($this->getPath())); + // attach the permissions to propagate etag on permision changes of submounts + $permissions = isset($data['permissions']) ? $data['permissions'] : 0; + $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions; + } + } + + /** + * @inheritdoc + */ + public function getChecksum() { + return $this->data['checksum']; + } + + public function getExtension(): string { + return pathinfo($this->getName(), PATHINFO_EXTENSION); + } + + public function getCreationTime(): int { + return (int) $this->data['creation_time']; + } + + public function getUploadTime(): int { + return (int) $this->data['upload_time']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Filesystem.php b/docker/overlays/nextcloud/html/lib/private/Files/Filesystem.php new file mode 100644 index 0000000..0a0707d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Filesystem.php @@ -0,0 +1,923 @@ + + * @author Bart Visscher + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Florin Peter + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author korelstar + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Stephan Peijnik + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * post_initMountPoints(user, user_dir) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ + +namespace OC\Files; + +use OC\Cache\CappedMemoryCache; +use OC\Files\Config\MountProviderCollection; +use OC\Files\Mount\MountPoint; +use OC\Lockdown\Filesystem\NullStorage; +use OCP\Files\Config\IMountProvider; +use OCP\Files\NotFoundException; +use OCP\Files\Storage\IStorageFactory; +use OCP\ILogger; +use OCP\IUserManager; + +class Filesystem { + + /** + * @var Mount\Manager $mounts + */ + private static $mounts; + + public static $loaded = false; + /** + * @var \OC\Files\View $defaultInstance + */ + private static $defaultInstance; + + private static $usersSetup = []; + + private static $normalizedPathCache = null; + + private static $listeningForProviders = false; + + /** + * classname which used for hooks handling + * used as signalclass in OC_Hooks::emit() + */ + public const CLASSNAME = 'OC_Filesystem'; + + /** + * signalname emitted before file renaming + * + * @param string $oldpath + * @param string $newpath + */ + public const signal_rename = 'rename'; + + /** + * signal emitted after file renaming + * + * @param string $oldpath + * @param string $newpath + */ + public const signal_post_rename = 'post_rename'; + + /** + * signal emitted before file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_create = 'create'; + + /** + * signal emitted after file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_post_create = 'post_create'; + + /** + * signal emits before file/dir copy + * + * @param string $oldpath + * @param string $newpath + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_copy = 'copy'; + + /** + * signal emits after file/dir copy + * + * @param string $oldpath + * @param string $newpath + */ + public const signal_post_copy = 'post_copy'; + + /** + * signal emits before file/dir save + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_write = 'write'; + + /** + * signal emits after file/dir save + * + * @param string $path + */ + public const signal_post_write = 'post_write'; + + /** + * signal emitted before file/dir update + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_update = 'update'; + + /** + * signal emitted after file/dir update + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + public const signal_post_update = 'post_update'; + + /** + * signal emits when reading file/dir + * + * @param string $path + */ + public const signal_read = 'read'; + + /** + * signal emits when removing file/dir + * + * @param string $path + */ + public const signal_delete = 'delete'; + + /** + * parameters definitions for signals + */ + public const signal_param_path = 'path'; + public const signal_param_oldpath = 'oldpath'; + public const signal_param_newpath = 'newpath'; + + /** + * run - changing this flag to false in hook handler will cancel event + */ + public const signal_param_run = 'run'; + + public const signal_create_mount = 'create_mount'; + public const signal_delete_mount = 'delete_mount'; + public const signal_param_mount_type = 'mounttype'; + public const signal_param_users = 'users'; + + /** + * @var \OC\Files\Storage\StorageFactory $loader + */ + private static $loader; + + /** @var bool */ + private static $logWarningWhenAddingStorageWrapper = true; + + /** + * @param bool $shouldLog + * @return bool previous value + * @internal + */ + public static function logWarningWhenAddingStorageWrapper($shouldLog) { + $previousValue = self::$logWarningWhenAddingStorageWrapper; + self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog; + return $previousValue; + } + + /** + * @param string $wrapperName + * @param callable $wrapper + * @param int $priority + */ + public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) { + if (self::$logWarningWhenAddingStorageWrapper) { + \OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [ + 'wrapper' => $wrapperName, + 'app' => 'filesystem', + ]); + } + + $mounts = self::getMountManager()->getAll(); + if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) { + // do not re-wrap if storage with this name already existed + return; + } + } + + /** + * Returns the storage factory + * + * @return IStorageFactory + */ + public static function getLoader() { + if (!self::$loader) { + self::$loader = \OC::$server->query(IStorageFactory::class); + } + return self::$loader; + } + + /** + * Returns the mount manager + * + * @return \OC\Files\Mount\Manager + */ + public static function getMountManager($user = '') { + if (!self::$mounts) { + \OC_Util::setupFS($user); + } + return self::$mounts; + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and doesn't take the chroot into account ) + * + * @param string $path + * @return string + */ + public static function getMountPoint($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return $mount->getMountPoint(); + } else { + return ''; + } + } + + /** + * get a list of all mount points in a directory + * + * @param string $path + * @return string[] + */ + public static function getMountPoints($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $result = []; + $mounts = self::$mounts->findIn($path); + foreach ($mounts as $mount) { + $result[] = $mount->getMountPoint(); + } + return $result; + } + + /** + * get the storage mounted at $mountPoint + * + * @param string $mountPoint + * @return \OC\Files\Storage\Storage + */ + public static function getStorage($mountPoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($mountPoint); + return $mount->getStorage(); + } + + /** + * @param string $id + * @return Mount\MountPoint[] + */ + public static function getMountByStorageId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByStorageId($id); + } + + /** + * @param int $id + * @return Mount\MountPoint[] + */ + public static function getMountByNumericId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByNumericId($id); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array an array consisting of the storage and the internal path + */ + public static function resolvePath($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')]; + } else { + return [null, null]; + } + } + + public static function init($user, $root) { + if (self::$defaultInstance) { + return false; + } + self::getLoader(); + self::$defaultInstance = new View($root); + + if (!self::$mounts) { + self::$mounts = \OC::$server->getMountManager(); + } + + //load custom mount config + self::initMountPoints($user); + + self::$loaded = true; + + return true; + } + + public static function initMountManager() { + if (!self::$mounts) { + self::$mounts = \OC::$server->getMountManager(); + } + } + + /** + * Initialize system and personal mount points for a user + * + * @param string $user + * @throws \OC\User\NoUserException if the user is not available + */ + public static function initMountPoints($user = '') { + if ($user == '') { + $user = \OC_User::getUser(); + } + if ($user === null || $user === false || $user === '') { + throw new \OC\User\NoUserException('Attempted to initialize mount points for null user and no user in session'); + } + + if (isset(self::$usersSetup[$user])) { + return; + } + + self::$usersSetup[$user] = true; + + $userManager = \OC::$server->getUserManager(); + $userObject = $userManager->get($user); + + if (is_null($userObject)) { + \OCP\Util::writeLog('files', ' Backends provided no user object for ' . $user, ILogger::ERROR); + // reset flag, this will make it possible to rethrow the exception if called again + unset(self::$usersSetup[$user]); + throw new \OC\User\NoUserException('Backends provided no user object for ' . $user); + } + + $realUid = $userObject->getUID(); + // workaround in case of different casings + if ($user !== $realUid) { + $stack = json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 50)); + \OCP\Util::writeLog('files', 'initMountPoints() called with wrong user casing. This could be a bug. Expected: "' . $realUid . '" got "' . $user . '". Stack: ' . $stack, ILogger::WARN); + $user = $realUid; + + // again with the correct casing + if (isset(self::$usersSetup[$user])) { + return; + } + + self::$usersSetup[$user] = true; + } + + if (\OC::$server->getLockdownManager()->canAccessFilesystem()) { + /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ + $mountConfigManager = \OC::$server->getMountProviderCollection(); + + // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers + $homeMount = $mountConfigManager->getHomeMountForUser($userObject); + self::getMountManager()->addMount($homeMount); + + if ($homeMount->getStorageRootId() === -1) { + $homeMount->getStorage()->mkdir(''); + $homeMount->getStorage()->getScanner()->scan(''); + } + + \OC\Files\Filesystem::getStorage($user); + + // Chance to mount for other storages + if ($userObject) { + $mounts = $mountConfigManager->addMountForUser($userObject, self::getMountManager()); + $mounts[] = $homeMount; + $mountConfigManager->registerMounts($userObject, $mounts); + } + + self::listenForNewMountProviders($mountConfigManager, $userManager); + } else { + self::getMountManager()->addMount(new MountPoint( + new NullStorage([]), + '/' . $user + )); + self::getMountManager()->addMount(new MountPoint( + new NullStorage([]), + '/' . $user . '/files' + )); + } + \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user]); + } + + /** + * Get mounts from mount providers that are registered after setup + * + * @param MountProviderCollection $mountConfigManager + * @param IUserManager $userManager + */ + private static function listenForNewMountProviders(MountProviderCollection $mountConfigManager, IUserManager $userManager) { + if (!self::$listeningForProviders) { + self::$listeningForProviders = true; + $mountConfigManager->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) use ($userManager) { + foreach (Filesystem::$usersSetup as $user => $setup) { + $userObject = $userManager->get($user); + if ($userObject) { + $mounts = $provider->getMountsForUser($userObject, Filesystem::getLoader()); + array_walk($mounts, [self::$mounts, 'addMount']); + } + } + }); + } + } + + /** + * get the default filesystem view + * + * @return View + */ + public static function getView() { + return self::$defaultInstance; + } + + /** + * tear down the filesystem, removing all storage providers + */ + public static function tearDown() { + self::clearMounts(); + self::$defaultInstance = null; + } + + /** + * get the relative path of the root data directory for the current user + * + * @return string + * + * Returns path like /admin/files + */ + public static function getRoot() { + if (!self::$defaultInstance) { + return null; + } + return self::$defaultInstance->getRoot(); + } + + /** + * clear all mounts and storage backends + */ + public static function clearMounts() { + if (self::$mounts) { + self::$usersSetup = []; + self::$mounts->clear(); + } + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @param \OC\Files\Storage\Storage|string $class + * @param array $arguments + * @param string $mountpoint + */ + public static function mount($class, $arguments, $mountpoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader()); + self::$mounts->addMount($mount); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + public static function getLocalFile($path) { + return self::$defaultInstance->getLocalFile($path); + } + + /** + * @param string $path + * @return string + */ + public static function getLocalFolder($path) { + return self::$defaultInstance->getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @param string $path + * @return string + */ + public static function getLocalPath($path) { + $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; + $newpath = $path; + if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { + $newpath = substr($path, strlen($datadir)); + } + return $newpath; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + public static function isValidPath($path) { + $path = self::normalizePath($path); + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @param array $data from hook + */ + public static function isBlacklisted($data) { + if (isset($data['path'])) { + $path = $data['path']; + } elseif (isset($data['newpath'])) { + $path = $data['newpath']; + } + if (isset($path)) { + if (self::isFileBlacklisted($path)) { + $data['run'] = false; + } + } + } + + /** + * @param string $filename + * @return bool + */ + public static function isFileBlacklisted($filename) { + $filename = self::normalizePath($filename); + + $blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']); + $filename = strtolower(basename($filename)); + return in_array($filename, $blacklist); + } + + /** + * check if the directory should be ignored when scanning + * NOTE: the special directories . and .. would cause never ending recursion + * + * @param string $dir + * @return boolean + */ + public static function isIgnoredDir($dir) { + if ($dir === '.' || $dir === '..') { + return true; + } + return false; + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + */ + public static function mkdir($path) { + return self::$defaultInstance->mkdir($path); + } + + public static function rmdir($path) { + return self::$defaultInstance->rmdir($path); + } + + public static function is_dir($path) { + return self::$defaultInstance->is_dir($path); + } + + public static function is_file($path) { + return self::$defaultInstance->is_file($path); + } + + public static function stat($path) { + return self::$defaultInstance->stat($path); + } + + public static function filetype($path) { + return self::$defaultInstance->filetype($path); + } + + public static function filesize($path) { + return self::$defaultInstance->filesize($path); + } + + public static function readfile($path) { + return self::$defaultInstance->readfile($path); + } + + public static function isCreatable($path) { + return self::$defaultInstance->isCreatable($path); + } + + public static function isReadable($path) { + return self::$defaultInstance->isReadable($path); + } + + public static function isUpdatable($path) { + return self::$defaultInstance->isUpdatable($path); + } + + public static function isDeletable($path) { + return self::$defaultInstance->isDeletable($path); + } + + public static function isSharable($path) { + return self::$defaultInstance->isSharable($path); + } + + public static function file_exists($path) { + return self::$defaultInstance->file_exists($path); + } + + public static function filemtime($path) { + return self::$defaultInstance->filemtime($path); + } + + public static function touch($path, $mtime = null) { + return self::$defaultInstance->touch($path, $mtime); + } + + /** + * @return string + */ + public static function file_get_contents($path) { + return self::$defaultInstance->file_get_contents($path); + } + + public static function file_put_contents($path, $data) { + return self::$defaultInstance->file_put_contents($path, $data); + } + + public static function unlink($path) { + return self::$defaultInstance->unlink($path); + } + + public static function rename($path1, $path2) { + return self::$defaultInstance->rename($path1, $path2); + } + + public static function copy($path1, $path2) { + return self::$defaultInstance->copy($path1, $path2); + } + + public static function fopen($path, $mode) { + return self::$defaultInstance->fopen($path, $mode); + } + + /** + * @return string + */ + public static function toTmpFile($path) { + return self::$defaultInstance->toTmpFile($path); + } + + public static function fromTmpFile($tmpFile, $path) { + return self::$defaultInstance->fromTmpFile($tmpFile, $path); + } + + public static function getMimeType($path) { + return self::$defaultInstance->getMimeType($path); + } + + public static function hash($type, $path, $raw = false) { + return self::$defaultInstance->hash($type, $path, $raw); + } + + public static function free_space($path = '/') { + return self::$defaultInstance->free_space($path); + } + + public static function search($query) { + return self::$defaultInstance->search($query); + } + + /** + * @param string $query + */ + public static function searchByMime($query) { + return self::$defaultInstance->searchByMime($query); + } + + /** + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return FileInfo[] array or file info + */ + public static function searchByTag($tag, $userId) { + return self::$defaultInstance->searchByTag($tag, $userId); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public static function hasUpdated($path, $time) { + return self::$defaultInstance->hasUpdated($path, $time); + } + + /** + * Fix common problems with a file path + * + * @param string $path + * @param bool $stripTrailingSlash whether to strip the trailing slash + * @param bool $isAbsolutePath whether the given path is absolute + * @param bool $keepUnicode true to disable unicode normalization + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) { + if (is_null(self::$normalizedPathCache)) { + self::$normalizedPathCache = new CappedMemoryCache(2048); + } + + /** + * FIXME: This is a workaround for existing classes and files which call + * this function with another type than a valid string. This + * conversion should get removed as soon as all existing + * function calls have been fixed. + */ + $path = (string)$path; + + $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]); + + if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) { + return self::$normalizedPathCache[$cacheKey]; + } + + if ($path === '') { + return '/'; + } + + //normalize unicode if possible + if (!$keepUnicode) { + $path = \OC_Util::normalizeUnicode($path); + } + + //add leading slash, if it is already there we strip it anyway + $path = '/' . $path; + + $patterns = [ + '/\\\\/s', // no windows style slashes + '/\/\.(\/\.)?\//s', // remove '/./' + '/\/{2,}/s', // remove sequence of slashes + '/\/\.$/s', // remove trailing /. + ]; + + do { + $count = 0; + $path = preg_replace($patterns, '/', $path, -1, $count); + } while ($count > 0); + + //remove trailing slash + if ($stripTrailingSlash && strlen($path) > 1) { + $path = rtrim($path, '/'); + } + + self::$normalizedPathCache[$cacheKey] = $path; + + return $path; + } + + /** + * get the filesystem info + * + * @param string $path + * @param boolean $includeMountPoints whether to add mountpoint sizes, + * defaults to true + * @return \OC\Files\FileInfo|bool False if file does not exist + */ + public static function getFileInfo($path, $includeMountPoints = true) { + return self::$defaultInstance->getFileInfo($path, $includeMountPoints); + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public static function putFileInfo($path, $data) { + return self::$defaultInstance->putFileInfo($path, $data); + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return \OC\Files\FileInfo[] + */ + public static function getDirectoryContent($directory, $mimetype_filter = '') { + return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); + } + + /** + * Get the path of a file by id + * + * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @throws NotFoundException + * @return string + */ + public static function getPath($id) { + return self::$defaultInstance->getPath($id); + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public static function getOwner($path) { + return self::$defaultInstance->getOwner($path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public static function getETag($path) { + return self::$defaultInstance->getETag($path); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/CacheMountProvider.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/CacheMountProvider.php new file mode 100644 index 0000000..8e63264 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/CacheMountProvider.php @@ -0,0 +1,72 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use OCP\Files\Config\IMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use OCP\IUser; + +/** + * Mount provider for custom cache storages + */ +class CacheMountProvider implements IMountProvider { + /** + * @var IConfig + */ + private $config; + + /** + * ObjectStoreHomeMountProvider constructor. + * + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Get the cache mount for a user + * + * @param IUser $user + * @param IStorageFactory $loader + * @return \OCP\Files\Mount\IMountPoint[] + */ + public function getMountsForUser(IUser $user, IStorageFactory $loader) { + $cacheBaseDir = $this->config->getSystemValue('cache_path', ''); + if ($cacheBaseDir !== '') { + $cacheDir = rtrim($cacheBaseDir, '/') . '/' . $user->getUID(); + if (!file_exists($cacheDir)) { + mkdir($cacheDir, 0770, true); + mkdir($cacheDir . '/uploads', 0770, true); + } + + return [ + new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/cache', ['datadir' => $cacheDir, $loader]), + new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/uploads', ['datadir' => $cacheDir . '/uploads', $loader]) + ]; + } else { + return []; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/LocalHomeMountProvider.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/LocalHomeMountProvider.php new file mode 100644 index 0000000..68c69cc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/LocalHomeMountProvider.php @@ -0,0 +1,44 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use OCP\Files\Config\IHomeMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +/** + * Mount provider for regular posix home folders + */ +class LocalHomeMountProvider implements IHomeMountProvider { + /** + * Get the cache mount for a user + * + * @param IUser $user + * @param IStorageFactory $loader + * @return \OCP\Files\Mount\IMountPoint|null + */ + public function getHomeMountForUser(IUser $user, IStorageFactory $loader) { + $arguments = ['user' => $user]; + return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/Manager.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/Manager.php new file mode 100644 index 0000000..f3fa77a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/Manager.php @@ -0,0 +1,200 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use OC\Cache\CappedMemoryCache; +use OC\Files\Filesystem; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Mount\IMountPoint; + +class Manager implements IMountManager { + /** @var MountPoint[] */ + private $mounts = []; + + /** @var CappedMemoryCache */ + private $pathCache; + + /** @var CappedMemoryCache */ + private $inPathCache; + + public function __construct() { + $this->pathCache = new CappedMemoryCache(); + $this->inPathCache = new CappedMemoryCache(); + } + + /** + * @param IMountPoint $mount + */ + public function addMount(IMountPoint $mount) { + $this->mounts[$mount->getMountPoint()] = $mount; + $this->pathCache->clear(); + $this->inPathCache->clear(); + } + + /** + * @param string $mountPoint + */ + public function removeMount(string $mountPoint) { + $mountPoint = Filesystem::normalizePath($mountPoint); + if (\strlen($mountPoint) > 1) { + $mountPoint .= '/'; + } + unset($this->mounts[$mountPoint]); + $this->pathCache->clear(); + $this->inPathCache->clear(); + } + + /** + * @param string $mountPoint + * @param string $target + */ + public function moveMount(string $mountPoint, string $target) { + $this->mounts[$target] = $this->mounts[$mountPoint]; + unset($this->mounts[$mountPoint]); + $this->pathCache->clear(); + $this->inPathCache->clear(); + } + + /** + * Find the mount for $path + * + * @param string $path + * @return MountPoint|null + */ + public function find(string $path) { + \OC_Util::setupFS(); + $path = Filesystem::normalizePath($path); + + if (isset($this->pathCache[$path])) { + return $this->pathCache[$path]; + } + + $current = $path; + while (true) { + $mountPoint = $current . '/'; + if (isset($this->mounts[$mountPoint])) { + $this->pathCache[$path] = $this->mounts[$mountPoint]; + return $this->mounts[$mountPoint]; + } + + if ($current === '') { + return null; + } + + $current = dirname($current); + if ($current === '.' || $current === '/') { + $current = ''; + } + } + } + + /** + * Find all mounts in $path + * + * @param string $path + * @return MountPoint[] + */ + public function findIn(string $path): array { + \OC_Util::setupFS(); + $path = $this->formatPath($path); + + if (isset($this->inPathCache[$path])) { + return $this->inPathCache[$path]; + } + + $result = []; + $pathLength = \strlen($path); + $mountPoints = array_keys($this->mounts); + foreach ($mountPoints as $mountPoint) { + if (substr($mountPoint, 0, $pathLength) === $path && \strlen($mountPoint) > $pathLength) { + $result[] = $this->mounts[$mountPoint]; + } + } + + $this->inPathCache[$path] = $result; + return $result; + } + + public function clear() { + $this->mounts = []; + $this->pathCache->clear(); + $this->inPathCache->clear(); + } + + /** + * Find mounts by storage id + * + * @param string $id + * @return MountPoint[] + */ + public function findByStorageId(string $id): array { + \OC_Util::setupFS(); + if (\strlen($id) > 64) { + $id = md5($id); + } + $result = []; + foreach ($this->mounts as $mount) { + if ($mount->getStorageId() === $id) { + $result[] = $mount; + } + } + return $result; + } + + /** + * @return MountPoint[] + */ + public function getAll(): array { + return $this->mounts; + } + + /** + * Find mounts by numeric storage id + * + * @param int $id + * @return MountPoint[] + */ + public function findByNumericId(int $id): array { + $storageId = \OC\Files\Cache\Storage::getStorageId($id); + return $this->findByStorageId($storageId); + } + + /** + * @param string $path + * @return string + */ + private function formatPath(string $path): string { + $path = Filesystem::normalizePath($path); + if (\strlen($path) > 1) { + $path .= '/'; + } + return $path; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/MountPoint.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/MountPoint.php new file mode 100644 index 0000000..626bc32 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/MountPoint.php @@ -0,0 +1,284 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Georg Ehrke + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use OC\Files\Filesystem; +use OC\Files\Storage\Storage; +use OC\Files\Storage\StorageFactory; +use OCP\Files\Mount\IMountPoint; +use OCP\ILogger; + +class MountPoint implements IMountPoint { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage = null; + protected $class; + protected $storageId; + protected $rootId = null; + + /** + * Configuration options for the storage backend + * + * @var array + */ + protected $arguments = []; + protected $mountPoint; + + /** + * Mount specific options + * + * @var array + */ + protected $mountOptions = []; + + /** + * @var \OC\Files\Storage\StorageFactory $loader + */ + private $loader; + + /** + * Specified whether the storage is invalid after failing to + * instantiate it. + * + * @var bool + */ + private $invalidStorage = false; + + /** @var int|null */ + protected $mountId; + + /** + * @param string|\OC\Files\Storage\Storage $storage + * @param string $mountpoint + * @param array $arguments (optional) configuration for the storage backend + * @param \OCP\Files\Storage\IStorageFactory $loader + * @param array $mountOptions mount specific options + * @param int|null $mountId + * @throws \Exception + */ + public function __construct($storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null, $mountId = null) { + if (is_null($arguments)) { + $arguments = []; + } + if (is_null($loader)) { + $this->loader = new StorageFactory(); + } else { + $this->loader = $loader; + } + + if (!is_null($mountOptions)) { + $this->mountOptions = $mountOptions; + } + + $mountpoint = $this->formatPath($mountpoint); + $this->mountPoint = $mountpoint; + $this->mountId = $mountId; + if ($storage instanceof Storage) { + $this->class = get_class($storage); + $this->storage = $this->loader->wrap($this, $storage); + } else { + // Update old classes to new namespace + if (strpos($storage, 'OC_Filestorage_') !== false) { + $storage = '\OC\Files\Storage\\' . substr($storage, 15); + } + $this->class = $storage; + $this->arguments = $arguments; + } + } + + /** + * get complete path to the mount point, relative to data/ + * + * @return string + */ + public function getMountPoint() { + return $this->mountPoint; + } + + /** + * Sets the mount point path, relative to data/ + * + * @param string $mountPoint new mount point + */ + public function setMountPoint($mountPoint) { + $this->mountPoint = $this->formatPath($mountPoint); + } + + /** + * create the storage that is mounted + */ + private function createStorage() { + if ($this->invalidStorage) { + return; + } + + if (class_exists($this->class)) { + try { + $class = $this->class; + // prevent recursion by setting the storage before applying wrappers + $this->storage = new $class($this->arguments); + $this->storage = $this->loader->wrap($this, $this->storage); + } catch (\Exception $exception) { + $this->storage = null; + $this->invalidStorage = true; + if ($this->mountPoint === '/') { + // the root storage could not be initialized, show the user! + throw new \Exception('The root storage could not be initialized. Please contact your local administrator.', $exception->getCode(), $exception); + } else { + \OC::$server->getLogger()->logException($exception, ['level' => ILogger::ERROR]); + } + return; + } + } else { + \OCP\Util::writeLog('core', 'storage backend ' . $this->class . ' not found', ILogger::ERROR); + $this->invalidStorage = true; + return; + } + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getStorage() { + if (is_null($this->storage)) { + $this->createStorage(); + } + return $this->storage; + } + + /** + * @return string + */ + public function getStorageId() { + if (!$this->storageId) { + if (is_null($this->storage)) { + $storage = $this->createStorage(); //FIXME: start using exceptions + if (is_null($storage)) { + return null; + } + + $this->storage = $storage; + } + $this->storageId = $this->storage->getId(); + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + } + return $this->storageId; + } + + /** + * @return int + */ + public function getNumericStorageId() { + return $this->getStorage()->getStorageCache()->getNumericId(); + } + + /** + * @param string $path + * @return string + */ + public function getInternalPath($path) { + $path = Filesystem::normalizePath($path, true, false, true); + if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) { + $internalPath = ''; + } else { + $internalPath = substr($path, strlen($this->mountPoint)); + } + // substr returns false instead of an empty string, we always want a string + return (string)$internalPath; + } + + /** + * @param string $path + * @return string + */ + private function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } + + /** + * @param callable $wrapper + */ + public function wrapStorage($wrapper) { + $storage = $this->getStorage(); + // storage can be null if it couldn't be initialized + if ($storage != null) { + $this->storage = $wrapper($this->mountPoint, $storage, $this); + } + } + + /** + * Get a mount option + * + * @param string $name Name of the mount option to get + * @param mixed $default Default value for the mount option + * @return mixed + */ + public function getOption($name, $default) { + return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; + } + + /** + * Get all options for the mount + * + * @return array + */ + public function getOptions() { + return $this->mountOptions; + } + + /** + * Get the file id of the root of the storage + * + * @return int + */ + public function getStorageRootId() { + if (is_null($this->rootId) || $this->rootId === -1) { + $this->rootId = (int)$this->getStorage()->getCache()->getId(''); + } + return $this->rootId; + } + + public function getMountId() { + return $this->mountId; + } + + public function getMountType() { + return ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/MoveableMount.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/MoveableMount.php new file mode 100644 index 0000000..2c9cfc9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/MoveableMount.php @@ -0,0 +1,45 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +/** + * Defines the mount point to be (re)moved by the user + */ +interface MoveableMount { + /** + * Move the mount point to $target + * + * @param string $target the target mount point + * @return bool + */ + public function moveMount($target); + + /** + * Remove the mount points + * + * @return mixed + * @return bool + */ + public function removeMount(); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectHomeMountProvider.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectHomeMountProvider.php new file mode 100644 index 0000000..9b243d7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectHomeMountProvider.php @@ -0,0 +1,141 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vlastimil Pecinka + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use OCP\Files\Config\IHomeMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; + +/** + * Mount provider for object store home storages + */ +class ObjectHomeMountProvider implements IHomeMountProvider { + /** + * @var IConfig + */ + private $config; + + /** + * ObjectStoreHomeMountProvider constructor. + * + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Get the cache mount for a user + * + * @param IUser $user + * @param IStorageFactory $loader + * @return \OCP\Files\Mount\IMountPoint + */ + public function getHomeMountForUser(IUser $user, IStorageFactory $loader) { + $config = $this->getMultiBucketObjectStoreConfig($user); + if ($config === null) { + $config = $this->getSingleBucketObjectStoreConfig($user); + } + + if ($config === null) { + return null; + } + + return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader); + } + + /** + * @param IUser $user + * @return array|null + */ + private function getSingleBucketObjectStoreConfig(IUser $user) { + $config = $this->config->getSystemValue('objectstore'); + if (!is_array($config)) { + return null; + } + + // sanity checks + if (empty($config['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + $config['arguments']['user'] = $user; + + return $config; + } + + /** + * @param IUser $user + * @return array|null + */ + private function getMultiBucketObjectStoreConfig(IUser $user) { + $config = $this->config->getSystemValue('objectstore_multibucket'); + if (!is_array($config)) { + return null; + } + + // sanity checks + if (empty($config['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null); + + if ($bucket === null) { + /* + * Use any provided bucket argument as prefix + * and add the mapping from username => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + $mapper = new \OC\Files\ObjectStore\Mapper($user); + $numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64; + $config['arguments']['bucket'] .= $mapper->getBucket($numBuckets); + + $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']); + } else { + $config['arguments']['bucket'] = $bucket; + } + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + $config['arguments']['user'] = $user; + + return $config; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php new file mode 100644 index 0000000..4185c9b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php @@ -0,0 +1,151 @@ + + * + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Mount; + +use OC\Files\ObjectStore\AppdataPreviewObjectStoreStorage; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\Storage\Wrapper\Jail; +use OCP\Files\Config\IRootMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Mount provider for object store app data folder for previews + */ +class ObjectStorePreviewCacheMountProvider implements IRootMountProvider { + /** @var ILogger */ + private $logger; + /** @var IConfig */ + private $config; + + public function __construct(ILogger $logger, IConfig $config) { + $this->logger = $logger; + $this->config = $config; + } + + /** + * @return MountPoint[] + * @throws \Exception + */ + public function getRootMounts(IStorageFactory $loader): array { + if (!is_array($this->config->getSystemValue('objectstore_multibucket'))) { + return []; + } + if ($this->config->getSystemValue('objectstore.multibucket.preview-distribution', false) !== true) { + return []; + } + + $instanceId = $this->config->getSystemValueString('instanceid', ''); + $mountPoints = []; + $directoryRange = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + $i = 0; + foreach ($directoryRange as $parent) { + foreach ($directoryRange as $child) { + $mountPoints[] = new MountPoint( + AppdataPreviewObjectStoreStorage::class, + '/appdata_' . $instanceId . '/preview/' . $parent . '/' . $child, + $this->getMultiBucketObjectStore($i), + $loader + ); + $i++; + } + } + + $rootStorageArguments = $this->getMultiBucketObjectStoreForRoot(); + $fakeRootStorage = new ObjectStoreStorage($rootStorageArguments); + $fakeRootStorageJail = new Jail([ + 'storage' => $fakeRootStorage, + 'root' => '/appdata_' . $instanceId . '/preview', + ]); + + // add a fallback location to be able to fetch existing previews from the old bucket + $mountPoints[] = new MountPoint( + $fakeRootStorageJail, + '/appdata_' . $instanceId . '/preview/old-multibucket', + null, + $loader + ); + + return $mountPoints; + } + + protected function getMultiBucketObjectStore(int $number): array { + $config = $this->config->getSystemValue('objectstore_multibucket'); + + // sanity checks + if (empty($config['class'])) { + $this->logger->error('No class given for objectstore', ['app' => 'files']); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + /* + * Use any provided bucket argument as prefix + * and add the mapping from parent/child => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + + $config['arguments']['bucket'] .= "-preview-$number"; + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + $config['arguments']['internal-id'] = $number; + + return $config['arguments']; + } + + protected function getMultiBucketObjectStoreForRoot(): array { + $config = $this->config->getSystemValue('objectstore_multibucket'); + + // sanity checks + if (empty($config['class'])) { + $this->logger->error('No class given for objectstore', ['app' => 'files']); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + /* + * Use any provided bucket argument as prefix + * and add the mapping from parent/child => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + $config['arguments']['bucket'] .= '0'; + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + return $config['arguments']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/File.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/File.php new file mode 100644 index 0000000..915e336 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/File.php @@ -0,0 +1,161 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\GenericFileException; +use OCP\Files\NotPermittedException; +use OCP\Lock\LockedException; + +class File extends Node implements \OCP\Files\File { + /** + * Creates a Folder that represents a non-existing path + * + * @param string $path path + * @return string non-existing node class + */ + protected function createNonExistingNode($path) { + return new NonExistingFile($this->root, $this->view, $path); + } + + /** + * @return string + * @throws NotPermittedException + * @throws LockedException + */ + public function getContent() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_READ)) { + /** + * @var \OC\Files\Storage\Storage $storage; + */ + return $this->view->file_get_contents($this->path); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string|resource $data + * @throws NotPermittedException + * @throws \OCP\Files\GenericFileException + * @throws LockedException + */ + public function putContent($data) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { + $this->sendHooks(['preWrite']); + if ($this->view->file_put_contents($this->path, $data) === false) { + throw new GenericFileException('file_put_contents failed'); + } + $this->fileInfo = null; + $this->sendHooks(['postWrite']); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $mode + * @return resource + * @throws NotPermittedException + * @throws LockedException + */ + public function fopen($mode) { + $preHooks = []; + $postHooks = []; + $requiredPermissions = \OCP\Constants::PERMISSION_READ; + switch ($mode) { + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $preHooks[] = 'preWrite'; + $postHooks[] = 'postWrite'; + $requiredPermissions |= \OCP\Constants::PERMISSION_UPDATE; + break; + } + + if ($this->checkPermissions($requiredPermissions)) { + $this->sendHooks($preHooks); + $result = $this->view->fopen($this->path, $mode); + $this->sendHooks($postHooks); + return $result; + } else { + throw new NotPermittedException(); + } + } + + /** + * @throws NotPermittedException + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException + */ + public function delete() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { + $this->sendHooks(['preDelete']); + $fileInfo = $this->getFileInfo(); + $this->view->unlink($this->path); + $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); + $this->sendHooks(['postDelete'], [$nonExisting]); + $this->exists = false; + $this->fileInfo = null; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $type + * @param bool $raw + * @return string + */ + public function hash($type, $raw = false) { + return $this->view->hash($type, $this->path, $raw); + } + + /** + * @inheritdoc + */ + public function getChecksum() { + return $this->getFileInfo()->getChecksum(); + } + + public function getExtension(): string { + return $this->getFileInfo()->getExtension(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/Folder.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/Folder.php new file mode 100644 index 0000000..668893f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/Folder.php @@ -0,0 +1,579 @@ + + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\DB\QueryBuilder\Literal; +use OC\Files\Storage\Wrapper\Jail; +use OCA\Files_Sharing\SharedStorage; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\FileInfo; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\Search\ISearchQuery; + +class Folder extends Node implements \OCP\Files\Folder { + /** + * Creates a Folder that represents a non-existing path + * + * @param string $path path + * @return string non-existing node class + */ + protected function createNonExistingNode($path) { + return new NonExistingFolder($this->root, $this->view, $path); + } + + /** + * @param string $path path relative to the folder + * @return string + * @throws \OCP\Files\NotPermittedException + */ + public function getFullPath($path) { + if (!$this->isValidPath($path)) { + throw new NotPermittedException('Invalid path'); + } + return $this->path . $this->normalizePath($path); + } + + /** + * @param string $path + * @return string + */ + public function getRelativePath($path) { + if ($this->path === '' or $this->path === '/') { + return $this->normalizePath($path); + } + if ($path === $this->path) { + return '/'; + } elseif (strpos($path, $this->path . '/') !== 0) { + return null; + } else { + $path = substr($path, strlen($this->path)); + return $this->normalizePath($path); + } + } + + /** + * check if a node is a (grand-)child of the folder + * + * @param \OC\Files\Node\Node $node + * @return bool + */ + public function isSubNode($node) { + return strpos($node->getPath(), $this->path . '/') === 0; + } + + /** + * get the content of this directory + * + * @throws \OCP\Files\NotFoundException + * @return Node[] + */ + public function getDirectoryListing() { + $folderContent = $this->view->getDirectoryContent($this->path); + + return array_map(function (FileInfo $info) { + if ($info->getMimetype() === 'httpd/unix-directory') { + return new Folder($this->root, $this->view, $info->getPath(), $info); + } else { + return new File($this->root, $this->view, $info->getPath(), $info); + } + }, $folderContent); + } + + /** + * @param string $path + * @param FileInfo $info + * @return File|Folder + */ + protected function createNode($path, FileInfo $info = null) { + if (is_null($info)) { + $isDir = $this->view->is_dir($path); + } else { + $isDir = $info->getType() === FileInfo::TYPE_FOLDER; + } + if ($isDir) { + return new Folder($this->root, $this->view, $path, $info); + } else { + return new File($this->root, $this->view, $path, $info); + } + } + + /** + * Get the node at $path + * + * @param string $path + * @return \OC\Files\Node\Node + * @throws \OCP\Files\NotFoundException + */ + public function get($path) { + return $this->root->get($this->getFullPath($path)); + } + + /** + * @param string $path + * @return bool + */ + public function nodeExists($path) { + try { + $this->get($path); + return true; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * @param string $path + * @return \OC\Files\Node\Folder + * @throws \OCP\Files\NotPermittedException + */ + public function newFolder($path) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); + if (!$this->view->mkdir($fullPath)) { + throw new NotPermittedException('Could not create folder'); + } + $node = new Folder($this->root, $this->view, $fullPath); + $this->sendHooks(['postWrite', 'postCreate'], [$node]); + return $node; + } else { + throw new NotPermittedException('No create permission for folder'); + } + } + + /** + * @param string $path + * @param string | resource | null $content + * @return \OC\Files\Node\File + * @throws \OCP\Files\NotPermittedException + */ + public function newFile($path, $content = null) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); + if ($content !== null) { + $result = $this->view->file_put_contents($fullPath, $content); + } else { + $result = $this->view->touch($fullPath); + } + if ($result === false) { + throw new NotPermittedException('Could not create path'); + } + $node = new File($this->root, $this->view, $fullPath); + $this->sendHooks(['postWrite', 'postCreate'], [$node]); + return $node; + } + throw new NotPermittedException('No create permission for path'); + } + + /** + * search for files with the name matching $query + * + * @param string|ISearchQuery $query + * @return \OC\Files\Node\Node[] + */ + public function search($query) { + if (is_string($query)) { + return $this->searchCommon('search', ['%' . $query . '%']); + } else { + return $this->searchCommon('searchQuery', [$query]); + } + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return Node[] + */ + public function searchByMime($mimetype) { + return $this->searchCommon('searchByMime', [$mimetype]); + } + + /** + * search for files by tag + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return Node[] + */ + public function searchByTag($tag, $userId) { + return $this->searchCommon('searchByTag', [$tag, $userId]); + } + + /** + * @param string $method cache method + * @param array $args call args + * @return \OC\Files\Node\Node[] + */ + private function searchCommon($method, $args) { + $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false; + if ($limitToHome && count(explode('/', $this->path)) !== 3) { + throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); + } + + $files = []; + $rootLength = strlen($this->path); + $mount = $this->root->getMount($this->path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($this->path); + $internalPath = rtrim($internalPath, '/'); + if ($internalPath !== '') { + $internalPath = $internalPath . '/'; + } + $internalRootLength = strlen($internalPath); + + $cache = $storage->getCache(''); + + $results = call_user_func_array([$cache, $method], $args); + foreach ($results as $result) { + if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { + $result['internalPath'] = $result['path']; + $result['path'] = substr($result['path'], $internalRootLength); + $result['storage'] = $storage; + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); + } + } + + if (!$limitToHome) { + $mounts = $this->root->getMountsIn($this->path); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); + $results = call_user_func_array([$cache, $method], $args); + foreach ($results as $result) { + $result['internalPath'] = $result['path']; + $result['path'] = $relativeMountPoint . $result['path']; + $result['storage'] = $storage; + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, + $result['internalPath'], $result, $mount); + } + } + } + } + + return array_map(function (FileInfo $file) { + return $this->createNode($file->getPath(), $file); + }, $files); + } + + /** + * @param int $id + * @return \OC\Files\Node\Node[] + */ + public function getById($id) { + $mountCache = $this->root->getUserMountCache(); + if (strpos($this->getPath(), '/', 1) > 0) { + list(, $user) = explode('/', $this->getPath()); + } else { + $user = null; + } + $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user); + $mounts = $this->root->getMountsIn($this->path); + $mounts[] = $this->root->getMount($this->path); + /** @var IMountPoint[] $folderMounts */ + $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) { + return $mountPoint->getMountPoint(); + }, $mounts), $mounts); + + /** @var ICachedMountInfo[] $mountsContainingFile */ + $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) { + return isset($folderMounts[$cachedMountInfo->getMountPoint()]); + })); + + if (count($mountsContainingFile) === 0) { + if ($user === $this->getAppDataDirectoryName()) { + return $this->getByIdInRootMount((int) $id); + } + return []; + } + + $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) { + $mount = $folderMounts[$cachedMountInfo->getMountPoint()]; + $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); + if (!$cacheEntry) { + return null; + } + + // cache jails will hide the "true" internal path + $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/'); + $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath())); + $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); + $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/'); + return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( + $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, + \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) + )); + }, $mountsContainingFile); + + $nodes = array_filter($nodes); + + return array_filter($nodes, function (Node $node) { + return $this->getRelativePath($node->getPath()); + }); + } + + protected function getAppDataDirectoryName(): string { + $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid'); + return 'appdata_' . $instanceId; + } + + /** + * In case the path we are currently in is inside the appdata_* folder, + * the original getById method does not work, because it can only look inside + * the user's mount points. But the user has no mount point for the root storage. + * + * So in that case we directly check the mount of the root if it contains + * the id. If it does we check if the path is inside the path we are working + * in. + * + * @param int $id + * @return array + */ + protected function getByIdInRootMount(int $id): array { + $mount = $this->root->getMount(''); + $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id); + if (!$cacheEntry) { + return []; + } + + $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/'); + $currentPath = rtrim($this->path, '/') . '/'; + + if (strpos($absolutePath, $currentPath) !== 0) { + return []; + } + + return [$this->root->createNode( + $absolutePath, new \OC\Files\FileInfo( + $absolutePath, + $mount->getStorage(), + $cacheEntry->getPath(), + $cacheEntry, + $mount + ))]; + } + + public function getFreeSpace() { + return $this->view->free_space($this->path); + } + + public function delete() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { + $this->sendHooks(['preDelete']); + $fileInfo = $this->getFileInfo(); + $this->view->rmdir($this->path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); + $this->sendHooks(['postDelete'], [$nonExisting]); + $this->exists = false; + } else { + throw new NotPermittedException('No delete permission for path'); + } + } + + /** + * Add a suffix to the name in case the file exists + * + * @param string $name + * @return string + * @throws NotPermittedException + */ + public function getNonExistingName($name) { + $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); + return trim($this->getRelativePath($uniqueName), '/'); + } + + /** + * @param int $limit + * @param int $offset + * @return \OCP\Files\Node[] + */ + public function getRecent($limit, $offset = 0) { + $mimetypeLoader = \OC::$server->getMimeTypeLoader(); + $mounts = $this->root->getMountsIn($this->path); + $mounts[] = $this->getMountPoint(); + + $mounts = array_filter($mounts, function (IMountPoint $mount) { + return $mount->getStorage(); + }); + $storageIds = array_map(function (IMountPoint $mount) { + return $mount->getStorage()->getCache()->getNumericStorageId(); + }, $mounts); + /** @var IMountPoint[] $mountMap */ + $mountMap = array_combine($storageIds, $mounts); + $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER); + + /* + * Construct an array of the storage id with their prefix path + * This helps us to filter in the final query + */ + $filters = array_map(function (IMountPoint $mount) { + $storage = $mount->getStorage(); + + $storageId = $storage->getCache()->getNumericStorageId(); + $prefix = ''; + + if ($storage->instanceOfStorage(Jail::class)) { + $prefix = $storage->getUnJailedPath(''); + } + + return [ + 'storageId' => $storageId, + 'pathPrefix' => $prefix, + ]; + }, $mounts); + + // Search in batches of 500 entries + $searchLimit = 500; + $results = []; + $searchResultCount = 0; + $count = 0; + do { + $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters); + + // Exit condition if there are no more results + if (count($searchResult) === 0) { + break; + } + + $searchResultCount += count($searchResult); + + $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader); + + foreach ($parseResult as $result) { + $results[] = $result; + } + + $offset += $searchLimit; + $count++; + } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5)); + + return array_slice($results, 0, $limit); + } + + private function recentSearch($limit, $offset, $folderMimetype, $filters) { + $dbconn = \OC::$server->getDatabaseConnection(); + $builder = $dbconn->getQueryBuilder(); + $query = $builder + ->select('f.*') + ->from('filecache', 'f'); + + /* + * Here is where we construct the filtering. + * Note that this is expensive filtering as it is a lot of like queries. + * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly + */ + $storageFilters = $builder->expr()->orX(); + foreach ($filters as $filter) { + $storageFilter = $builder->expr()->andX( + $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId'])) + ); + + if ($filter['pathPrefix'] !== '') { + $storageFilter->add( + $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%')) + ); + } + + $storageFilters->add($storageFilter); + } + + $query->andWhere($storageFilters); + + $query->andWhere($builder->expr()->orX( + // handle non empty folders separate + $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)), + $builder->expr()->eq('f.size', new Literal(0)) + )) + ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%'))) + ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%'))) + ->orderBy('f.mtime', 'DESC') + ->setMaxResults($limit) + ->setFirstResult($offset); + + return $query->execute()->fetchAll(); + } + + private function recentParse($result, $mountMap, $mimetypeLoader) { + $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) { + $mount = $mountMap[$entry['storage']]; + $entry['internalPath'] = $entry['path']; + $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']); + $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']); + $path = $this->getAbsolutePath($mount, $entry['path']); + if (is_null($path)) { + return null; + } + $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount); + return $this->root->createNode($fileInfo->getPath(), $fileInfo); + }, $result)); + + return array_values(array_filter($files, function (Node $node) { + $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId()); + if (!$cacheEntry) { + return false; + } + $relative = $this->getRelativePath($node->getPath()); + return $relative !== null && $relative !== '/' + && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ; + })); + } + + private function getAbsolutePath(IMountPoint $mount, $path) { + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) { + if ($storage->instanceOfStorage(SharedStorage::class)) { + $storage->getSourceStorage(); + } + /** @var \OC\Files\Storage\Wrapper\Jail $storage */ + $jailRoot = $storage->getUnjailedPath(''); + $rootLength = strlen($jailRoot) + 1; + if ($path === $jailRoot) { + return $mount->getMountPoint(); + } elseif (substr($path, 0, $rootLength) === $jailRoot . '/') { + return $mount->getMountPoint() . substr($path, $rootLength); + } else { + return null; + } + } else { + return $mount->getMountPoint() . $path; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/HookConnector.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/HookConnector.php new file mode 100644 index 0000000..2f26418 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/HookConnector.php @@ -0,0 +1,248 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\EventDispatcher\GenericEvent; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\Node\BeforeNodeCopiedEvent; +use OCP\Files\Events\Node\BeforeNodeCreatedEvent; +use OCP\Files\Events\Node\BeforeNodeDeletedEvent; +use OCP\Files\Events\Node\BeforeNodeReadEvent; +use OCP\Files\Events\Node\BeforeNodeRenamedEvent; +use OCP\Files\Events\Node\BeforeNodeTouchedEvent; +use OCP\Files\Events\Node\BeforeNodeWrittenEvent; +use OCP\Files\Events\Node\NodeCopiedEvent; +use OCP\Files\Events\Node\NodeCreatedEvent; +use OCP\Files\Events\Node\NodeDeletedEvent; +use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Events\Node\NodeTouchedEvent; +use OCP\Files\Events\Node\NodeWrittenEvent; +use OCP\Files\FileInfo; +use OCP\Files\IRootFolder; +use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class HookConnector { + /** @var IRootFolder */ + private $root; + + /** @var View */ + private $view; + + /** @var FileInfo[] */ + private $deleteMetaCache = []; + + /** @var EventDispatcherInterface */ + private $legacyDispatcher; + + /** @var IEventDispatcher */ + private $dispatcher; + + /** + * HookConnector constructor. + * + * @param Root $root + * @param View $view + */ + public function __construct( + IRootFolder $root, + View $view, + EventDispatcherInterface $legacyDispatcher, + IEventDispatcher $dispatcher) { + $this->root = $root; + $this->view = $view; + $this->legacyDispatcher = $legacyDispatcher; + $this->dispatcher = $dispatcher; + } + + public function viewToNode() { + Util::connectHook('OC_Filesystem', 'write', $this, 'write'); + Util::connectHook('OC_Filesystem', 'post_write', $this, 'postWrite'); + + Util::connectHook('OC_Filesystem', 'create', $this, 'create'); + Util::connectHook('OC_Filesystem', 'post_create', $this, 'postCreate'); + + Util::connectHook('OC_Filesystem', 'delete', $this, 'delete'); + Util::connectHook('OC_Filesystem', 'post_delete', $this, 'postDelete'); + + Util::connectHook('OC_Filesystem', 'rename', $this, 'rename'); + Util::connectHook('OC_Filesystem', 'post_rename', $this, 'postRename'); + + Util::connectHook('OC_Filesystem', 'copy', $this, 'copy'); + Util::connectHook('OC_Filesystem', 'post_copy', $this, 'postCopy'); + + Util::connectHook('OC_Filesystem', 'touch', $this, 'touch'); + Util::connectHook('OC_Filesystem', 'post_touch', $this, 'postTouch'); + + Util::connectHook('OC_Filesystem', 'read', $this, 'read'); + } + + public function write($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preWrite', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::preWrite', new GenericEvent($node)); + + $event = new BeforeNodeWrittenEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function postWrite($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postWrite', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::postWrite', new GenericEvent($node)); + + $event = new NodeWrittenEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function create($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preCreate', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::preCreate', new GenericEvent($node)); + + $event = new BeforeNodeCreatedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function postCreate($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postCreate', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::postCreate', new GenericEvent($node)); + + $event = new NodeCreatedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function delete($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->deleteMetaCache[$node->getPath()] = $node->getFileInfo(); + $this->root->emit('\OC\Files', 'preDelete', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node)); + + $event = new BeforeNodeDeletedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function postDelete($arguments) { + $node = $this->getNodeForPath($arguments['path']); + unset($this->deleteMetaCache[$node->getPath()]); + $this->root->emit('\OC\Files', 'postDelete', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::postDelete', new GenericEvent($node)); + + $event = new NodeDeletedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function touch($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preTouch', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::preTouch', new GenericEvent($node)); + + $event = new BeforeNodeTouchedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function postTouch($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postTouch', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::postTouch', new GenericEvent($node)); + + $event = new NodeTouchedEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + public function rename($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'preRename', [$source, $target]); + $this->legacyDispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target])); + + $event = new BeforeNodeRenamedEvent($source, $target); + $this->dispatcher->dispatchTyped($event); + } + + public function postRename($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'postRename', [$source, $target]); + $this->legacyDispatcher->dispatch('\OCP\Files::postRename', new GenericEvent([$source, $target])); + + $event = new NodeRenamedEvent($source, $target); + $this->dispatcher->dispatchTyped($event); + } + + public function copy($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'preCopy', [$source, $target]); + $this->legacyDispatcher->dispatch('\OCP\Files::preCopy', new GenericEvent([$source, $target])); + + $event = new BeforeNodeCopiedEvent($source, $target); + $this->dispatcher->dispatchTyped($event); + } + + public function postCopy($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); + $this->legacyDispatcher->dispatch('\OCP\Files::postCopy', new GenericEvent([$source, $target])); + + $event = new NodeCopiedEvent($source, $target); + $this->dispatcher->dispatchTyped($event); + } + + public function read($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'read', [$node]); + $this->legacyDispatcher->dispatch('\OCP\Files::read', new GenericEvent([$node])); + + $event = new BeforeNodeReadEvent($node); + $this->dispatcher->dispatchTyped($event); + } + + private function getNodeForPath($path) { + $info = Filesystem::getView()->getFileInfo($path); + if (!$info) { + $fullPath = Filesystem::getView()->getAbsolutePath($path); + if (isset($this->deleteMetaCache[$fullPath])) { + $info = $this->deleteMetaCache[$fullPath]; + } else { + $info = null; + } + if (Filesystem::is_dir($path)) { + return new NonExistingFolder($this->root, $this->view, $fullPath, $info); + } else { + return new NonExistingFile($this->root, $this->view, $fullPath, $info); + } + } + if ($info->getType() === FileInfo::TYPE_FILE) { + return new File($this->root, $this->view, $info->getPath(), $info); + } else { + return new Folder($this->root, $this->view, $info->getPath(), $info); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyFolder.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyFolder.php new file mode 100644 index 0000000..52dfb3c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyFolder.php @@ -0,0 +1,499 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Node; + +/** + * Class LazyFolder + * + * This is a lazy wrapper around a folder. So only + * once it is needed this will get initialized. + * + * @package OC\Files\Node + */ +class LazyFolder implements \OCP\Files\Folder { + /** @var \Closure */ + private $folderClosure; + + /** @var LazyFolder | null */ + private $folder = null; + + /** + * LazyFolder constructor. + * + * @param \Closure $folderClosure + */ + public function __construct(\Closure $folderClosure) { + $this->folderClosure = $folderClosure; + } + + /** + * Magic method to first get the real rootFolder and then + * call $method with $args on it + * + * @param $method + * @param $args + * @return mixed + */ + public function __call($method, $args) { + if ($this->folder === null) { + $this->folder = call_user_func($this->folderClosure); + } + + return call_user_func_array([$this->folder, $method], $args); + } + + /** + * @inheritDoc + */ + public function getUser() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function listen($scope, $method, callable $callback) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function emit($scope, $method, $arguments = []) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function mount($storage, $mountPoint, $arguments = []) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMount($mountPoint) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountsIn($mountPoint) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountByStorageId($storageId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountByNumericStorageId($numericId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function unMount($mount) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function get($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function rename($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function delete() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function copy($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function touch($mtime = null) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getStorage() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getPath() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getInternalPath() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getId() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function stat() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMTime() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getSize($includeMounts = true) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getEtag() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getPermissions() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isReadable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isUpdateable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isDeletable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isShareable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getParent() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getName() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getUserFolder($userId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMimetype() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMimePart() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isEncrypted() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getType() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isShared() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isMounted() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountPoint() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getOwner() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getChecksum() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function getExtension(): string { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getFullPath($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getRelativePath($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isSubNode($node) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getDirectoryListing() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function nodeExists($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function newFolder($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function newFile($path, $content = null) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function search($query) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function searchByMime($mimetype) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function searchByTag($tag, $userId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getById($id) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getFreeSpace() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isCreatable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getNonExistingName($name) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function move($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function lock($type) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function changeLock($targetType) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function unlock($type) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getRecent($limit, $offset = 0) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getCreationTime(): int { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getUploadTime(): int { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyRoot.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyRoot.php new file mode 100644 index 0000000..899fd13 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/LazyRoot.php @@ -0,0 +1,43 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\IRootFolder; + +/** + * Class LazyRoot + * + * This is a lazy wrapper around the root. So only + * once it is needed this will get initialized. + * + * @package OC\Files\Node + */ +class LazyRoot extends LazyFolder implements IRootFolder { + /** + * @inheritDoc + */ + public function getUserFolder($userId) { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/Node.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/Node.php new file mode 100644 index 0000000..70821e6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/Node.php @@ -0,0 +1,468 @@ + + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\Files\Filesystem; +use OC\Files\Mount\MoveableMount; +use OCP\Files\FileInfo; +use OCP\Files\InvalidPathException; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Lock\LockedException; +use Symfony\Component\EventDispatcher\GenericEvent; + +// FIXME: this class really should be abstract +class Node implements \OCP\Files\Node { + /** + * @var \OC\Files\View $view + */ + protected $view; + + /** + * @var \OC\Files\Node\Root $root + */ + protected $root; + + /** + * @var string $path + */ + protected $path; + + /** + * @var \OCP\Files\FileInfo + */ + protected $fileInfo; + + /** + * @param \OC\Files\View $view + * @param \OCP\Files\IRootFolder $root + * @param string $path + * @param FileInfo $fileInfo + */ + public function __construct($root, $view, $path, $fileInfo = null) { + $this->view = $view; + $this->root = $root; + $this->path = $path; + $this->fileInfo = $fileInfo; + } + + /** + * Creates a Node of the same type that represents a non-existing path + * + * @param string $path path + * @return string non-existing node class + * @throws \Exception + */ + protected function createNonExistingNode($path) { + throw new \Exception('Must be implemented by subclasses'); + } + + /** + * Returns the matching file info + * + * @return FileInfo + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getFileInfo() { + if (!Filesystem::isValidPath($this->path)) { + throw new InvalidPathException(); + } + if (!$this->fileInfo) { + $fileInfo = $this->view->getFileInfo($this->path); + if ($fileInfo instanceof FileInfo) { + $this->fileInfo = $fileInfo; + } else { + throw new NotFoundException(); + } + } + return $this->fileInfo; + } + + /** + * @param string[] $hooks + */ + protected function sendHooks($hooks, array $args = null) { + $args = !empty($args) ? $args : [$this]; + $dispatcher = \OC::$server->getEventDispatcher(); + foreach ($hooks as $hook) { + $this->root->emit('\OC\Files', $hook, $args); + $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); + } + } + + /** + * @param int $permissions + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + protected function checkPermissions($permissions) { + return ($this->getPermissions() & $permissions) === $permissions; + } + + public function delete() { + } + + /** + * @param int $mtime + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException + */ + public function touch($mtime = null) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { + $this->sendHooks(['preTouch']); + $this->view->touch($this->path, $mtime); + $this->sendHooks(['postTouch']); + if ($this->fileInfo) { + if (is_null($mtime)) { + $mtime = time(); + } + $this->fileInfo['mtime'] = $mtime; + } + } else { + throw new NotPermittedException(); + } + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + list($storage,) = $this->view->resolvePath($this->path); + return $storage; + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return string + */ + public function getInternalPath() { + list(, $internalPath) = $this->view->resolvePath($this->path); + return $internalPath; + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getId() { + return $this->getFileInfo()->getId(); + } + + /** + * @return array + */ + public function stat() { + return $this->view->stat($this->path); + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getMTime() { + return $this->getFileInfo()->getMTime(); + } + + /** + * @param bool $includeMounts + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getSize($includeMounts = true) { + return $this->getFileInfo()->getSize($includeMounts); + } + + /** + * @return string + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getEtag() { + return $this->getFileInfo()->getEtag(); + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getPermissions() { + return $this->getFileInfo()->getPermissions(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isReadable() { + return $this->getFileInfo()->isReadable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isUpdateable() { + return $this->getFileInfo()->isUpdateable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isDeletable() { + return $this->getFileInfo()->isDeletable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isShareable() { + return $this->getFileInfo()->isShareable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isCreatable() { + return $this->getFileInfo()->isCreatable(); + } + + /** + * @return Node + */ + public function getParent() { + $newPath = dirname($this->path); + if ($newPath === '' || $newPath === '.' || $newPath === '/') { + return $this->root; + } + return $this->root->get($newPath); + } + + /** + * @return string + */ + public function getName() { + return basename($this->path); + } + + /** + * @param string $path + * @return string + */ + protected function normalizePath($path) { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + public function isValidPath($path) { + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + public function isMounted() { + return $this->getFileInfo()->isMounted(); + } + + public function isShared() { + return $this->getFileInfo()->isShared(); + } + + public function getMimeType() { + return $this->getFileInfo()->getMimetype(); + } + + public function getMimePart() { + return $this->getFileInfo()->getMimePart(); + } + + public function getType() { + return $this->getFileInfo()->getType(); + } + + public function isEncrypted() { + return $this->getFileInfo()->isEncrypted(); + } + + public function getMountPoint() { + return $this->getFileInfo()->getMountPoint(); + } + + public function getOwner() { + return $this->getFileInfo()->getOwner(); + } + + public function getChecksum() { + } + + public function getExtension(): string { + return $this->getFileInfo()->getExtension(); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws LockedException + */ + public function lock($type) { + $this->view->lockFile($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws LockedException + */ + public function changeLock($type) { + $this->view->changeLock($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws LockedException + */ + public function unlock($type) { + $this->view->unlockFile($this->path, $type); + } + + /** + * @param string $targetPath + * @return \OC\Files\Node\Node + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException if copy not allowed or failed + */ + public function copy($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = $this->createNonExistingNode($targetPath); + $this->sendHooks(['preCopy'], [$this, $nonExisting]); + $this->sendHooks(['preWrite'], [$nonExisting]); + if (!$this->view->copy($this->path, $targetPath)) { + throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); + } + $targetNode = $this->root->get($targetPath); + $this->sendHooks(['postCopy'], [$this, $targetNode]); + $this->sendHooks(['postWrite'], [$targetNode]); + return $targetNode; + } else { + throw new NotPermittedException('No permission to copy to path ' . $targetPath); + } + } + + /** + * @param string $targetPath + * @return \OC\Files\Node\Node + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException if move not allowed or failed + * @throws LockedException + */ + public function move($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ( + $parent instanceof Folder and + $this->isValidPath($targetPath) and + ( + $parent->isCreatable() || + ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount) + ) + ) { + $nonExisting = $this->createNonExistingNode($targetPath); + $this->sendHooks(['preRename'], [$this, $nonExisting]); + $this->sendHooks(['preWrite'], [$nonExisting]); + if (!$this->view->rename($this->path, $targetPath)) { + throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); + } + $targetNode = $this->root->get($targetPath); + $this->sendHooks(['postRename'], [$this, $targetNode]); + $this->sendHooks(['postWrite'], [$targetNode]); + $this->path = $targetPath; + return $targetNode; + } else { + throw new NotPermittedException('No permission to move to path ' . $targetPath); + } + } + + public function getCreationTime(): int { + return $this->getFileInfo()->getCreationTime(); + } + + public function getUploadTime(): int { + return $this->getFileInfo()->getUploadTime(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFile.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFile.php new file mode 100644 index 0000000..dc6a6f8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFile.php @@ -0,0 +1,144 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFile extends File { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + if ($this->fileInfo) { + return parent::getId(); + } else { + throw new NotFoundException(); + } + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + if ($this->fileInfo) { + return parent::getMTime(); + } else { + throw new NotFoundException(); + } + } + + public function getSize($includeMounts = true) { + if ($this->fileInfo) { + return parent::getSize($includeMounts); + } else { + throw new NotFoundException(); + } + } + + public function getEtag() { + if ($this->fileInfo) { + return parent::getEtag(); + } else { + throw new NotFoundException(); + } + } + + public function getPermissions() { + if ($this->fileInfo) { + return parent::getPermissions(); + } else { + throw new NotFoundException(); + } + } + + public function isReadable() { + if ($this->fileInfo) { + return parent::isReadable(); + } else { + throw new NotFoundException(); + } + } + + public function isUpdateable() { + if ($this->fileInfo) { + return parent::isUpdateable(); + } else { + throw new NotFoundException(); + } + } + + public function isDeletable() { + if ($this->fileInfo) { + return parent::isDeletable(); + } else { + throw new NotFoundException(); + } + } + + public function isShareable() { + if ($this->fileInfo) { + return parent::isShareable(); + } else { + throw new NotFoundException(); + } + } + + public function getContent() { + throw new NotFoundException(); + } + + public function putContent($data) { + throw new NotFoundException(); + } + + public function getMimeType() { + if ($this->fileInfo) { + return parent::getMimeType(); + } else { + throw new NotFoundException(); + } + } + + public function fopen($mode) { + throw new NotFoundException(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFolder.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFolder.php new file mode 100644 index 0000000..65af837 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/NonExistingFolder.php @@ -0,0 +1,173 @@ + + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFolder extends Folder { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + if ($this->fileInfo) { + return parent::getId(); + } else { + throw new NotFoundException(); + } + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + if ($this->fileInfo) { + return parent::getMTime(); + } else { + throw new NotFoundException(); + } + } + + public function getSize($includeMounts = true) { + if ($this->fileInfo) { + return parent::getSize($includeMounts); + } else { + throw new NotFoundException(); + } + } + + public function getEtag() { + if ($this->fileInfo) { + return parent::getEtag(); + } else { + throw new NotFoundException(); + } + } + + public function getPermissions() { + if ($this->fileInfo) { + return parent::getPermissions(); + } else { + throw new NotFoundException(); + } + } + + public function isReadable() { + if ($this->fileInfo) { + return parent::isReadable(); + } else { + throw new NotFoundException(); + } + } + + public function isUpdateable() { + if ($this->fileInfo) { + return parent::isUpdateable(); + } else { + throw new NotFoundException(); + } + } + + public function isDeletable() { + if ($this->fileInfo) { + return parent::isDeletable(); + } else { + throw new NotFoundException(); + } + } + + public function isShareable() { + if ($this->fileInfo) { + return parent::isShareable(); + } else { + throw new NotFoundException(); + } + } + + public function get($path) { + throw new NotFoundException(); + } + + public function getDirectoryListing() { + throw new NotFoundException(); + } + + public function nodeExists($path) { + return false; + } + + public function newFolder($path) { + throw new NotFoundException(); + } + + public function newFile($path, $content = null) { + throw new NotFoundException(); + } + + public function search($pattern) { + throw new NotFoundException(); + } + + public function searchByMime($mime) { + throw new NotFoundException(); + } + + public function searchByTag($tag, $userId) { + throw new NotFoundException(); + } + + public function getById($id) { + throw new NotFoundException(); + } + + public function getFreeSpace() { + throw new NotFoundException(); + } + + public function isCreatable() { + if ($this->fileInfo) { + return parent::isCreatable(); + } else { + throw new NotFoundException(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Node/Root.php b/docker/overlays/nextcloud/html/lib/private/Files/Node/Root.php new file mode 100644 index 0000000..f240828 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Node/Root.php @@ -0,0 +1,403 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\Cache\CappedMemoryCache; +use OC\Files\Mount\Manager; +use OC\Files\Mount\MountPoint; +use OC\Hooks\PublicEmitter; +use OC\User\NoUserException; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\ILogger; +use OCP\IUserManager; + +/** + * Class Root + * + * Hooks available in scope \OC\Files + * - preWrite(\OCP\Files\Node $node) + * - postWrite(\OCP\Files\Node $node) + * - preCreate(\OCP\Files\Node $node) + * - postCreate(\OCP\Files\Node $node) + * - preDelete(\OCP\Files\Node $node) + * - postDelete(\OCP\Files\Node $node) + * - preTouch(\OC\FilesP\Node $node, int $mtime) + * - postTouch(\OCP\Files\Node $node) + * - preCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - preRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * + * @package OC\Files\Node + */ +class Root extends Folder implements IRootFolder { + /** @var Manager */ + private $mountManager; + /** @var PublicEmitter */ + private $emitter; + /** @var null|\OC\User\User */ + private $user; + /** @var CappedMemoryCache */ + private $userFolderCache; + /** @var IUserMountCache */ + private $userMountCache; + /** @var ILogger */ + private $logger; + /** @var IUserManager */ + private $userManager; + + /** + * @param \OC\Files\Mount\Manager $manager + * @param \OC\Files\View $view + * @param \OC\User\User|null $user + * @param IUserMountCache $userMountCache + * @param ILogger $logger + * @param IUserManager $userManager + */ + public function __construct($manager, + $view, + $user, + IUserMountCache $userMountCache, + ILogger $logger, + IUserManager $userManager) { + parent::__construct($this, $view, ''); + $this->mountManager = $manager; + $this->user = $user; + $this->emitter = new PublicEmitter(); + $this->userFolderCache = new CappedMemoryCache(); + $this->userMountCache = $userMountCache; + $this->logger = $logger; + $this->userManager = $userManager; + } + + /** + * Get the user for which the filesystem is setup + * + * @return \OC\User\User + */ + public function getUser() { + return $this->user; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + $this->emitter->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->emitter->removeListener($scope, $method, $callback); + } + + /** + * @param string $scope + * @param string $method + * @param Node[] $arguments + */ + public function emit($scope, $method, $arguments = []) { + $this->emitter->emit($scope, $method, $arguments); + } + + /** + * @param \OC\Files\Storage\Storage $storage + * @param string $mountPoint + * @param array $arguments + */ + public function mount($storage, $mountPoint, $arguments = []) { + $mount = new MountPoint($storage, $mountPoint, $arguments); + $this->mountManager->addMount($mount); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\MountPoint + */ + public function getMount($mountPoint) { + return $this->mountManager->find($mountPoint); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\MountPoint[] + */ + public function getMountsIn($mountPoint) { + return $this->mountManager->findIn($mountPoint); + } + + /** + * @param string $storageId + * @return \OC\Files\Mount\MountPoint[] + */ + public function getMountByStorageId($storageId) { + return $this->mountManager->findByStorageId($storageId); + } + + /** + * @param int $numericId + * @return MountPoint[] + */ + public function getMountByNumericStorageId($numericId) { + return $this->mountManager->findByNumericId($numericId); + } + + /** + * @param \OC\Files\Mount\MountPoint $mount + */ + public function unMount($mount) { + $this->mountManager->remove($mount); + } + + /** + * @param string $path + * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\NotPermittedException + * @return string + */ + public function get($path) { + $path = $this->normalizePath($path); + if ($this->isValidPath($path)) { + $fullPath = $this->getFullPath($path); + $fileInfo = $this->view->getFileInfo($fullPath); + if ($fileInfo) { + return $this->createNode($fullPath, $fileInfo); + } else { + throw new NotFoundException($path); + } + } else { + throw new NotPermittedException(); + } + } + + //most operations can't be done on the root + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function rename($targetPath) { + throw new NotPermittedException(); + } + + public function delete() { + throw new NotPermittedException(); + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + throw new NotPermittedException(); + } + + /** + * @param int $mtime + * @throws \OCP\Files\NotPermittedException + */ + public function touch($mtime = null) { + throw new NotPermittedException(); + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getPath() { + return '/'; + } + + /** + * @return string + */ + public function getInternalPath() { + return ''; + } + + /** + * @return int + */ + public function getId() { + return null; + } + + /** + * @return array + */ + public function stat() { + return null; + } + + /** + * @return int + */ + public function getMTime() { + return null; + } + + /** + * @param bool $includeMounts + * @return int + */ + public function getSize($includeMounts = true) { + return null; + } + + /** + * @return string + */ + public function getEtag() { + return null; + } + + /** + * @return int + */ + public function getPermissions() { + return \OCP\Constants::PERMISSION_CREATE; + } + + /** + * @return bool + */ + public function isReadable() { + return false; + } + + /** + * @return bool + */ + public function isUpdateable() { + return false; + } + + /** + * @return bool + */ + public function isDeletable() { + return false; + } + + /** + * @return bool + */ + public function isShareable() { + return false; + } + + /** + * @return Node + * @throws \OCP\Files\NotFoundException + */ + public function getParent() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getName() { + return ''; + } + + /** + * Returns a view to user's files folder + * + * @param string $userId user ID + * @return \OCP\Files\Folder + * @throws NoUserException + * @throws NotPermittedException + */ + public function getUserFolder($userId) { + $userObject = $this->userManager->get($userId); + + if (is_null($userObject)) { + $this->logger->error( + sprintf( + 'Backends provided no user object for %s', + $userId + ), + [ + 'app' => 'files', + ] + ); + throw new NoUserException('Backends provided no user object'); + } + + $userId = $userObject->getUID(); + + if (!$this->userFolderCache->hasKey($userId)) { + \OC\Files\Filesystem::initMountPoints($userId); + + try { + $folder = $this->get('/' . $userId . '/files'); + } catch (NotFoundException $e) { + if (!$this->nodeExists('/' . $userId)) { + $this->newFolder('/' . $userId); + } + $folder = $this->newFolder('/' . $userId . '/files'); + } + + $this->userFolderCache->set($userId, $folder); + } + + return $this->userFolderCache->get($userId); + } + + public function clearCache() { + $this->userFolderCache = new CappedMemoryCache(); + } + + public function getUserMountCache() { + return $this->userMountCache; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Notify/Change.php b/docker/overlays/nextcloud/html/lib/private/Files/Notify/Change.php new file mode 100644 index 0000000..3cabbec --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Notify/Change.php @@ -0,0 +1,65 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Notify; + +use OCP\Files\Notify\IChange; + +class Change implements IChange { + /** @var int */ + private $type; + + /** @var string */ + private $path; + + /** + * Change constructor. + * + * @param int $type + * @param string $path + */ + public function __construct($type, $path) { + $this->type = $type; + $this->path = $path; + } + + /** + * Get the type of the change + * + * @return int IChange::ADDED, IChange::REMOVED, IChange::MODIFIED or IChange::RENAMED + */ + public function getType() { + return $this->type; + } + + /** + * Get the path of the file that was changed relative to the root of the storage + * + * Note, for rename changes this path is the old path for the file + * + * @return mixed + */ + public function getPath() { + return $this->path; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Notify/RenameChange.php b/docker/overlays/nextcloud/html/lib/private/Files/Notify/RenameChange.php new file mode 100644 index 0000000..225e41b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Notify/RenameChange.php @@ -0,0 +1,52 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Notify; + +use OCP\Files\Notify\IRenameChange; + +class RenameChange extends Change implements IRenameChange { + /** @var string */ + private $targetPath; + + /** + * Change constructor. + * + * @param int $type + * @param string $path + * @param string $targetPath + */ + public function __construct($type, $path, $targetPath) { + parent::__construct($type, $path); + $this->targetPath = $targetPath; + } + + /** + * Get the new path of the renamed file relative to the storage root + * + * @return string + */ + public function getTargetPath() { + return $this->targetPath; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php new file mode 100644 index 0000000..bdc41f9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php @@ -0,0 +1,45 @@ + + * + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +class AppdataPreviewObjectStoreStorage extends ObjectStoreStorage { + + /** @var string */ + private $internalId; + + public function __construct($params) { + if (!isset($params['internal-id'])) { + throw new \Exception('missing id in parameters'); + } + $this->internalId = (string)$params['internal-id']; + parent::__construct($params); + } + + public function getId() { + return 'object::appdata::preview:' . $this->internalId; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Azure.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Azure.php new file mode 100644 index 0000000..0b65a6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Azure.php @@ -0,0 +1,133 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use MicrosoftAzure\Storage\Blob\BlobRestProxy; +use MicrosoftAzure\Storage\Common\Exceptions\ServiceException; +use OCP\Files\ObjectStore\IObjectStore; + +class Azure implements IObjectStore { + /** @var string */ + private $containerName; + /** @var string */ + private $accountName; + /** @var string */ + private $accountKey; + /** @var BlobRestProxy|null */ + private $blobClient = null; + /** @var string|null */ + private $endpoint = null; + /** @var bool */ + private $autoCreate = false; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->containerName = $parameters['container']; + $this->accountName = $parameters['account_name']; + $this->accountKey = $parameters['account_key']; + if (isset($parameters['endpoint'])) { + $this->endpoint = $parameters['endpoint']; + } + if (isset($parameters['autocreate'])) { + $this->autoCreate = $parameters['autocreate']; + } + } + + /** + * @return BlobRestProxy + */ + private function getBlobClient() { + if (!$this->blobClient) { + $protocol = $this->endpoint ? substr($this->endpoint, 0, strpos($this->endpoint, ':')) : 'https'; + $connectionString = "DefaultEndpointsProtocol=" . $protocol . ";AccountName=" . $this->accountName . ";AccountKey=" . $this->accountKey; + if ($this->endpoint) { + $connectionString .= ';BlobEndpoint=' . $this->endpoint; + } + $this->blobClient = BlobRestProxy::createBlobService($connectionString); + + if ($this->autoCreate) { + try { + $this->blobClient->createContainer($this->containerName); + } catch (ServiceException $e) { + if ($e->getCode() === 409) { + // already exists + } else { + throw $e; + } + } + } + } + return $this->blobClient; + } + + /** + * @return string the container or bucket name where objects are stored + */ + public function getStorageId() { + return 'azure::blob::' . $this->containerName; + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return resource stream with the read data + * @throws \Exception when something goes wrong, message will be logged + */ + public function readObject($urn) { + $blob = $this->getBlobClient()->getBlob($this->containerName, $urn); + return $blob->getContentStream(); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @throws \Exception when something goes wrong, message will be logged + */ + public function writeObject($urn, $stream) { + $this->getBlobClient()->createBlockBlob($this->containerName, $urn, $stream); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return void + * @throws \Exception when something goes wrong, message will be logged + */ + public function deleteObject($urn) { + $this->getBlobClient()->deleteBlob($this->containerName, $urn); + } + + public function objectExists($urn) { + try { + $this->getBlobClient()->getBlobMetadata($this->containerName, $urn); + return true; + } catch (ServiceException $e) { + if ($e->getCode() === 404) { + return false; + } else { + throw $e; + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php new file mode 100644 index 0000000..7a95665 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php @@ -0,0 +1,69 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use OC\User\User; + +class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IHomeStorage { + + /** + * The home user storage requires a user object to create a unique storage id + * @param array $params + */ + public function __construct($params) { + if (! isset($params['user']) || ! $params['user'] instanceof User) { + throw new \Exception('missing user object in parameters'); + } + $this->user = $params['user']; + parent::__construct($params); + } + + public function getId() { + return 'object::user:' . $this->user->getUID(); + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return false|string uid + */ + public function getOwner($path) { + if (is_object($this->user)) { + return $this->user->getUID(); + } + return false; + } + + /** + * @param string $path, optional + * @return \OC\User\User + */ + public function getUser($path = null) { + return $this->user; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Mapper.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Mapper.php new file mode 100644 index 0000000..a518687 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Mapper.php @@ -0,0 +1,57 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use OCP\IUser; + +/** + * Class Mapper + * + * @package OC\Files\ObjectStore + * + * Map a user to a bucket. + */ +class Mapper { + /** @var IUser */ + private $user; + + /** + * Mapper constructor. + * + * @param IUser $user + */ + public function __construct(IUser $user) { + $this->user = $user; + } + + /** + * @param int $numBuckets + * @return string + */ + public function getBucket($numBuckets = 64) { + $hash = md5($this->user->getUID()); + $num = hexdec(substr($hash, 0, 4)); + return (string)($num % $numBuckets); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/NoopScanner.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/NoopScanner.php new file mode 100644 index 0000000..9195e7f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/NoopScanner.php @@ -0,0 +1,82 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use OC\Files\Cache\Scanner; +use OC\Files\Storage\Storage; + +class NoopScanner extends Scanner { + public function __construct(Storage $storage) { + //we don't need the storage, so do nothing here + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param int $parentId + * @param array|null $cacheData existing data in the cache for the file to be scanned + * @return array an array of metadata of the scanned file + */ + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) { + return []; + } + + /** + * scan a folder and all it's children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @return array with the meta data of the scanned file or folder + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { + return []; + } + + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param array $folderData existing cache data for the folder to be scanned + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) { + return 0; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + //noop + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/ObjectStoreStorage.php new file mode 100644 index 0000000..e675064 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -0,0 +1,526 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Marcel Klehr + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\CountWrapper; +use Icewind\Streams\IteratorDirectory; +use OC\Files\Cache\CacheEntry; +use OCP\Files\NotFoundException; +use OCP\Files\ObjectStore\IObjectStore; + +class ObjectStoreStorage extends \OC\Files\Storage\Common { + /** + * @var \OCP\Files\ObjectStore\IObjectStore $objectStore + */ + protected $objectStore; + /** + * @var string $id + */ + protected $id; + /** + * @var \OC\User\User $user + */ + protected $user; + + private $objectPrefix = 'urn:oid:'; + + private $logger; + + public function __construct($params) { + if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { + $this->objectStore = $params['objectstore']; + } else { + throw new \Exception('missing IObjectStore instance'); + } + if (isset($params['storageid'])) { + $this->id = 'object::store:' . $params['storageid']; + } else { + $this->id = 'object::store:' . $this->objectStore->getStorageId(); + } + if (isset($params['objectPrefix'])) { + $this->objectPrefix = $params['objectPrefix']; + } + //initialize cache with root directory in cache + if (!$this->is_dir('/')) { + $this->mkdir('/'); + } + + $this->logger = \OC::$server->getLogger(); + } + + public function mkdir($path) { + $path = $this->normalizePath($path); + + if ($this->file_exists($path)) { + return false; + } + + $mTime = time(); + $data = [ + 'mimetype' => 'httpd/unix-directory', + 'size' => 0, + 'mtime' => $mTime, + 'storage_mtime' => $mTime, + 'permissions' => \OCP\Constants::PERMISSION_ALL, + ]; + if ($path === '') { + //create root on the fly + $data['etag'] = $this->getETag(''); + $this->getCache()->put('', $data); + return true; + } else { + // if parent does not exist, create it + $parent = $this->normalizePath(dirname($path)); + $parentType = $this->filetype($parent); + if ($parentType === false) { + if (!$this->mkdir($parent)) { + // something went wrong + return false; + } + } elseif ($parentType === 'file') { + // parent is a file + return false; + } + // finally create the new dir + $mTime = time(); // update mtime + $data['mtime'] = $mTime; + $data['storage_mtime'] = $mTime; + $data['etag'] = $this->getETag($path); + $this->getCache()->put($path, $data); + return true; + } + } + + /** + * @param string $path + * @return string + */ + private function normalizePath($path) { + $path = trim($path, '/'); + //FIXME why do we sometimes get a path like 'files//username'? + $path = str_replace('//', '/', $path); + + // dirname('/folder') returns '.' but internally (in the cache) we store the root as '' + if (!$path || $path === '.') { + $path = ''; + } + + return $path; + } + + /** + * Object Stores use a NoopScanner because metadata is directly stored in + * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere. + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\ObjectStore\NoopScanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->scanner)) { + $this->scanner = new NoopScanner($storage); + } + return $this->scanner; + } + + public function getId() { + return $this->id; + } + + public function rmdir($path) { + $path = $this->normalizePath($path); + + if (!$this->is_dir($path)) { + return false; + } + + if (!$this->rmObjects($path)) { + return false; + } + + $this->getCache()->remove($path); + + return true; + } + + private function rmObjects($path) { + $children = $this->getCache()->getFolderContents($path); + foreach ($children as $child) { + if ($child['mimetype'] === 'httpd/unix-directory') { + if (!$this->rmObjects($child['path'])) { + return false; + } + } else { + if (!$this->unlink($child['path'])) { + return false; + } + } + } + + return true; + } + + public function unlink($path) { + $path = $this->normalizePath($path); + $stat = $this->stat($path); + + if ($stat && isset($stat['fileid'])) { + if ($stat['mimetype'] === 'httpd/unix-directory') { + return $this->rmdir($path); + } + try { + $this->objectStore->deleteObject($this->getURN($stat['fileid'])); + } catch (\Exception $ex) { + if ($ex->getCode() !== 404) { + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path, + ]); + return false; + } + //removing from cache is ok as it does not exist in the objectstore anyway + } + $this->getCache()->remove($path); + return true; + } + return false; + } + + public function stat($path) { + $path = $this->normalizePath($path); + $cacheEntry = $this->getCache()->get($path); + if ($cacheEntry instanceof CacheEntry) { + return $cacheEntry->getData(); + } else { + return false; + } + } + + public function getPermissions($path) { + $stat = $this->stat($path); + + if (is_array($stat) && isset($stat['permissions'])) { + return $stat['permissions']; + } + + return parent::getPermissions($path); + } + + /** + * Override this method if you need a different unique resource identifier for your object storage implementation. + * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. + * You may need a mapping table to store your URN if it cannot be generated from the fileid. + * + * @param int $fileId the fileid + * @return null|string the unified resource name used to identify the object + */ + public function getURN($fileId) { + if (is_numeric($fileId)) { + return $this->objectPrefix . $fileId; + } + return null; + } + + public function opendir($path) { + $path = $this->normalizePath($path); + + try { + $files = []; + $folderContents = $this->getCache()->getFolderContents($path); + foreach ($folderContents as $file) { + $files[] = $file['name']; + } + + return IteratorDirectory::wrap($files); + } catch (\Exception $e) { + $this->logger->logException($e); + return false; + } + } + + public function filetype($path) { + $path = $this->normalizePath($path); + $stat = $this->stat($path); + if ($stat) { + if ($stat['mimetype'] === 'httpd/unix-directory') { + return 'dir'; + } + return 'file'; + } else { + return false; + } + } + + public function fopen($path, $mode) { + $path = $this->normalizePath($path); + + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + + switch ($mode) { + case 'r': + case 'rb': + $stat = $this->stat($path); + if (is_array($stat)) { + // Reading 0 sized files is a waste of time + if (isset($stat['size']) && $stat['size'] === 0) { + return fopen('php://memory', $mode); + } + + try { + return $this->objectStore->readObject($this->getURN($stat['fileid'])); + } catch (NotFoundException $e) { + $this->logger->logException($e, [ + 'app' => 'objectstore', + 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, + ]); + throw $e; + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, + ]); + return false; + } + } else { + return false; + } + // no break + case 'w': + case 'wb': + case 'w+': + case 'wb+': + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + $this->writeBack($tmpFile, $path); + }); + case 'a': + case 'ab': + case 'r+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + if ($this->file_exists($path)) { + $source = $this->fopen($path, 'r'); + file_put_contents($tmpFile, $source); + } + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + $this->writeBack($tmpFile, $path); + }); + } + return false; + } + + public function file_exists($path) { + $path = $this->normalizePath($path); + return (bool)$this->stat($path); + } + + public function rename($source, $target) { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); + $this->remove($target); + $this->getCache()->move($source, $target); + $this->touch(dirname($target)); + return true; + } + + public function getMimeType($path) { + $path = $this->normalizePath($path); + return parent::getMimeType($path); + } + + public function touch($path, $mtime = null) { + if (is_null($mtime)) { + $mtime = time(); + } + + $path = $this->normalizePath($path); + $dirName = dirname($path); + $parentExists = $this->is_dir($dirName); + if (!$parentExists) { + return false; + } + + $stat = $this->stat($path); + if (is_array($stat)) { + // update existing mtime in db + $stat['mtime'] = $mtime; + $this->getCache()->update($stat['fileid'], $stat); + } else { + try { + //create a empty file, need to have at least on char to make it + // work with all object storage implementations + $this->file_put_contents($path, ' '); + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); + $stat = [ + 'etag' => $this->getETag($path), + 'mimetype' => $mimeType, + 'size' => 0, + 'mtime' => $mtime, + 'storage_mtime' => $mtime, + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, + ]; + $this->getCache()->put($path, $stat); + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not create object for ' . $path, + ]); + throw $ex; + } + } + return true; + } + + public function writeBack($tmpFile, $path) { + $size = filesize($tmpFile); + $this->writeStream($path, fopen($tmpFile, 'r'), $size); + } + + /** + * external changes are not supported, exclusive access to the object storage is assumed + * + * @param string $path + * @param int $time + * @return false + */ + public function hasUpdated($path, $time) { + return false; + } + + public function needsPartFile() { + return false; + } + + public function file_put_contents($path, $data) { + $handle = $this->fopen($path, 'w+'); + fwrite($handle, $data); + fclose($handle); + return true; + } + + public function writeStream(string $path, $stream, int $size = null): int { + $stat = $this->stat($path); + if (empty($stat)) { + // create new file + $stat = [ + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, + ]; + } + // update stat with new data + $mTime = time(); + $stat['size'] = (int)$size; + $stat['mtime'] = $mTime; + $stat['storage_mtime'] = $mTime; + + $mimetypeDetector = \OC::$server->getMimeTypeDetector(); + $mimetype = $mimetypeDetector->detectPath($path); + + $stat['mimetype'] = $mimetype; + $stat['etag'] = $this->getETag($path); + + $exists = $this->getCache()->inCache($path); + $uploadPath = $exists ? $path : $path . '.part'; + + if ($exists) { + $fileId = $stat['fileid']; + } else { + $fileId = $this->getCache()->put($uploadPath, $stat); + } + + $urn = $this->getURN($fileId); + try { + //upload to object storage + if ($size === null) { + $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) { + $this->getCache()->update($fileId, [ + 'size' => $writtenSize + ]); + $size = $writtenSize; + }); + $this->objectStore->writeObject($urn, $countStream); + if (is_resource($countStream)) { + fclose($countStream); + } + $stat['size'] = $size; + } else { + $this->objectStore->writeObject($urn, $stream); + } + } catch (\Exception $ex) { + if (!$exists) { + /* + * Only remove the entry if we are dealing with a new file. + * Else people lose access to existing files + */ + $this->getCache()->remove($uploadPath); + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not create object ' . $urn . ' for ' . $path, + ]); + } else { + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not update object ' . $urn . ' for ' . $path, + ]); + } + throw $ex; // make this bubble up + } + + if ($exists) { + $this->getCache()->update($fileId, $stat); + } else { + if ($this->objectStore->objectExists($urn)) { + $this->getCache()->move($uploadPath, $path); + } else { + $this->getCache()->remove($uploadPath); + throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404); + } + } + + return $size; + } + + public function getObjectStore(): IObjectStore { + return $this->objectStore; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3.php new file mode 100644 index 0000000..3d1a658 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3.php @@ -0,0 +1,44 @@ + + * + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use OCP\Files\ObjectStore\IObjectStore; + +class S3 implements IObjectStore { + use S3ConnectionTrait; + use S3ObjectTrait; + + public function __construct($parameters) { + $this->parseParams($parameters); + } + + /** + * @return string the container or bucket name where objects are stored + * @since 7.0.0 + */ + public function getStorageId() { + return $this->id; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ConnectionTrait.php new file mode 100644 index 0000000..467ae81 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -0,0 +1,197 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Florent + * @author Morris Jobke + * @author Robin Appelman + * @author S. Cat <33800996+sparrowjack63@users.noreply.github.com> + * @author Stephen Cuppett + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use Aws\ClientResolver; +use Aws\Credentials\CredentialProvider; +use Aws\Credentials\Credentials; +use Aws\Exception\CredentialsException; +use Aws\S3\Exception\S3Exception; +use Aws\S3\S3Client; +use GuzzleHttp\Promise; +use GuzzleHttp\Promise\RejectedPromise; +use OCP\ILogger; + +trait S3ConnectionTrait { + /** @var array */ + protected $params; + + /** @var S3Client */ + protected $connection; + + /** @var string */ + protected $id; + + /** @var string */ + protected $bucket; + + /** @var int */ + protected $timeout; + + /** @var int */ + protected $uploadPartSize; + + protected $test; + + protected function parseParams($params) { + if (empty($params['bucket'])) { + throw new \Exception("Bucket has to be configured."); + } + + $this->id = 'amazon::' . $params['bucket']; + + $this->test = isset($params['test']); + $this->bucket = $params['bucket']; + $this->timeout = !isset($params['timeout']) ? 15 : $params['timeout']; + $this->uploadPartSize = !isset($params['uploadPartSize']) ? 524288000 : $params['uploadPartSize']; + $params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region']; + $params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname']; + if (!isset($params['port']) || $params['port'] === '') { + $params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443; + } + $this->params = $params; + } + + public function getBucket() { + return $this->bucket; + } + + /** + * Returns the connection + * + * @return S3Client connected client + * @throws \Exception if connection could not be made + */ + public function getConnection() { + if (!is_null($this->connection)) { + return $this->connection; + } + + $scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https'; + $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/'; + + // Adding explicit credential provider to the beginning chain. + // Including environment variables and IAM instance profiles. + $provider = CredentialProvider::memoize( + CredentialProvider::chain( + $this->paramCredentialProvider(), + CredentialProvider::env(), + CredentialProvider::instanceProfile() + ) + ); + + $options = [ + 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest', + 'credentials' => $provider, + 'endpoint' => $base_url, + 'region' => $this->params['region'], + 'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false, + 'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()), + 'csm' => false, + ]; + if (isset($this->params['proxy'])) { + $options['request.options'] = ['proxy' => $this->params['proxy']]; + } + if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) { + $options['signature_version'] = 'v2'; + } + $this->connection = new S3Client($options); + + if (!$this->connection::isBucketDnsCompatible($this->bucket)) { + $logger = \OC::$server->getLogger(); + $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.', + ['app' => 'objectstore']); + } + + if (!$this->connection->doesBucketExist($this->bucket)) { + $logger = \OC::$server->getLogger(); + try { + $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']); + if (!$this->connection::isBucketDnsCompatible($this->bucket)) { + throw new \Exception("The bucket will not be created because the name is not dns compatible, please correct it: " . $this->bucket); + } + $this->connection->createBucket(['Bucket' => $this->bucket]); + $this->testTimeout(); + } catch (S3Exception $e) { + $logger->logException($e, [ + 'message' => 'Invalid remote storage.', + 'level' => ILogger::DEBUG, + 'app' => 'objectstore', + ]); + throw new \Exception('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage()); + } + } + + // google cloud's s3 compatibility doesn't like the EncodingType parameter + if (strpos($base_url, 'storage.googleapis.com')) { + $this->connection->getHandlerList()->remove('s3.auto_encode'); + } + + return $this->connection; + } + + /** + * when running the tests wait to let the buckets catch up + */ + private function testTimeout() { + if ($this->test) { + sleep($this->timeout); + } + } + + public static function legacySignatureProvider($version, $service, $region) { + switch ($version) { + case 'v2': + case 's3': + return new S3Signature(); + default: + return null; + } + } + + /** + * This function creates a credential provider based on user parameter file + */ + protected function paramCredentialProvider() : callable { + return function () { + $key = empty($this->params['key']) ? null : $this->params['key']; + $secret = empty($this->params['secret']) ? null : $this->params['secret']; + + if ($key && $secret) { + return Promise\promise_for( + new Credentials($key, $secret) + ); + } + + $msg = 'Could not find parameters set for credentials in config file.'; + return new RejectedPromise(new CredentialsException($msg)); + }; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ObjectTrait.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ObjectTrait.php new file mode 100644 index 0000000..a390c6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -0,0 +1,127 @@ + + * + * @author Christoph Wurst + * @author Florent + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use Aws\S3\Exception\S3MultipartUploadException; +use Aws\S3\MultipartUploader; +use Aws\S3\ObjectUploader; +use Aws\S3\S3Client; +use Icewind\Streams\CallbackWrapper; +use OC\Files\Stream\SeekableHttpStream; + +trait S3ObjectTrait { + /** + * Returns the connection + * + * @return S3Client connected client + * @throws \Exception if connection could not be made + */ + abstract protected function getConnection(); + + /** + * @param string $urn the unified resource name used to identify the object + * @return resource stream with the read data + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function readObject($urn) { + return SeekableHttpStream::open(function ($range) use ($urn) { + $command = $this->getConnection()->getCommand('GetObject', [ + 'Bucket' => $this->bucket, + 'Key' => $urn, + 'Range' => 'bytes=' . $range, + ]); + $request = \Aws\serialize($command); + $headers = []; + foreach ($request->getHeaders() as $key => $values) { + foreach ($values as $value) { + $headers[] = "$key: $value"; + } + } + $opts = [ + 'http' => [ + 'protocol_version' => 1.1, + 'header' => $headers, + ], + ]; + + $context = stream_context_create($opts); + return fopen($request->getUri(), 'r', false, $context); + }); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function writeObject($urn, $stream) { + $count = 0; + $countStream = CallbackWrapper::wrap($stream, function ($read) use (&$count) { + $count += $read; + }); + + $uploader = new MultipartUploader($this->getConnection(), $countStream, [ + 'bucket' => $this->bucket, + 'key' => $urn, + 'part_size' => $this->uploadPartSize, + ]); + + try { + $uploader->upload(); + } catch (S3MultipartUploadException $e) { + // This is an empty file so just touch it then + if ($count === 0 && feof($countStream)) { + $uploader = new ObjectUploader($this->getConnection(), $this->bucket, $urn, ''); + $uploader->upload(); + } else { + throw $e; + } + } + + fclose($countStream); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return void + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function deleteObject($urn) { + $this->getConnection()->deleteObject([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + ]); + } + + public function objectExists($urn) { + return $this->getConnection()->doesObjectExist($this->bucket, $urn); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3Signature.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3Signature.php new file mode 100644 index 0000000..f83b0e0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/S3Signature.php @@ -0,0 +1,222 @@ + + * @author Daniel Kesselberg + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\Files\ObjectStore; + +use Aws\Credentials\CredentialsInterface; +use Aws\S3\S3Client; +use Aws\S3\S3UriParser; +use Aws\Signature\SignatureInterface; +use GuzzleHttp\Psr7; +use Psr\Http\Message\RequestInterface; + +/** + * Legacy Amazon S3 signature implementation + */ +class S3Signature implements SignatureInterface { + /** @var array Query string values that must be signed */ + private $signableQueryString = [ + 'acl', 'cors', 'delete', 'lifecycle', 'location', 'logging', + 'notification', 'partNumber', 'policy', 'requestPayment', + 'response-cache-control', 'response-content-disposition', + 'response-content-encoding', 'response-content-language', + 'response-content-type', 'response-expires', 'restore', 'tagging', + 'torrent', 'uploadId', 'uploads', 'versionId', 'versioning', + 'versions', 'website' + ]; + + /** @var array Sorted headers that must be signed */ + private $signableHeaders = ['Content-MD5', 'Content-Type']; + + /** @var \Aws\S3\S3UriParser S3 URI parser */ + private $parser; + + public function __construct() { + $this->parser = new S3UriParser(); + // Ensure that the signable query string parameters are sorted + sort($this->signableQueryString); + } + + public function signRequest( + RequestInterface $request, + CredentialsInterface $credentials + ) { + $request = $this->prepareRequest($request, $credentials); + $stringToSign = $this->createCanonicalizedString($request); + $auth = 'AWS ' + . $credentials->getAccessKeyId() . ':' + . $this->signString($stringToSign, $credentials); + + return $request->withHeader('Authorization', $auth); + } + + public function presign( + RequestInterface $request, + CredentialsInterface $credentials, + $expires, + array $options = [] + ) { + $query = []; + // URL encoding already occurs in the URI template expansion. Undo that + // and encode using the same encoding as GET object, PUT object, etc. + $uri = $request->getUri(); + $path = S3Client::encodeKey(rawurldecode($uri->getPath())); + $request = $request->withUri($uri->withPath($path)); + + // Make sure to handle temporary credentials + if ($token = $credentials->getSecurityToken()) { + $request = $request->withHeader('X-Amz-Security-Token', $token); + $query['X-Amz-Security-Token'] = $token; + } + + if ($expires instanceof \DateTime) { + $expires = $expires->getTimestamp(); + } elseif (!is_numeric($expires)) { + $expires = strtotime($expires); + } + + // Set query params required for pre-signed URLs + $query['AWSAccessKeyId'] = $credentials->getAccessKeyId(); + $query['Expires'] = $expires; + $query['Signature'] = $this->signString( + $this->createCanonicalizedString($request, $expires), + $credentials + ); + + // Move X-Amz-* headers to the query string + foreach ($request->getHeaders() as $name => $header) { + $name = strtolower($name); + if (strpos($name, 'x-amz-') === 0) { + $query[$name] = implode(',', $header); + } + } + + $queryString = http_build_query($query, null, '&', PHP_QUERY_RFC3986); + + return $request->withUri($request->getUri()->withQuery($queryString)); + } + + /** + * @param RequestInterface $request + * @param CredentialsInterface $creds + * + * @return RequestInterface + */ + private function prepareRequest( + RequestInterface $request, + CredentialsInterface $creds + ) { + $modify = [ + 'remove_headers' => ['X-Amz-Date'], + 'set_headers' => ['Date' => gmdate(\DateTime::RFC2822)] + ]; + + // Add the security token header if one is being used by the credentials + if ($token = $creds->getSecurityToken()) { + $modify['set_headers']['X-Amz-Security-Token'] = $token; + } + + return Psr7\modify_request($request, $modify); + } + + private function signString($string, CredentialsInterface $credentials) { + return base64_encode( + hash_hmac('sha1', $string, $credentials->getSecretKey(), true) + ); + } + + private function createCanonicalizedString( + RequestInterface $request, + $expires = null + ) { + $buffer = $request->getMethod() . "\n"; + + // Add the interesting headers + foreach ($this->signableHeaders as $header) { + $buffer .= $request->getHeaderLine($header) . "\n"; + } + + $date = $expires ?: $request->getHeaderLine('date'); + $buffer .= "{$date}\n" + . $this->createCanonicalizedAmzHeaders($request) + . $this->createCanonicalizedResource($request); + + return $buffer; + } + + private function createCanonicalizedAmzHeaders(RequestInterface $request) { + $headers = []; + foreach ($request->getHeaders() as $name => $header) { + $name = strtolower($name); + if (strpos($name, 'x-amz-') === 0) { + $value = implode(',', $header); + if (strlen($value) > 0) { + $headers[$name] = $name . ':' . $value; + } + } + } + + if (!$headers) { + return ''; + } + + ksort($headers); + + return implode("\n", $headers) . "\n"; + } + + private function createCanonicalizedResource(RequestInterface $request) { + $data = $this->parser->parse($request->getUri()); + $buffer = '/'; + + if ($data['bucket']) { + $buffer .= $data['bucket']; + if (!empty($data['key']) || !$data['path_style']) { + $buffer .= '/' . $data['key']; + } + } + + // Add sub resource parameters if present. + $query = $request->getUri()->getQuery(); + + if ($query) { + $params = Psr7\parse_query($query); + $first = true; + foreach ($this->signableQueryString as $key) { + if (array_key_exists($key, $params)) { + $value = $params[$key]; + $buffer .= $first ? '?' : '&'; + $first = false; + $buffer .= $key; + // Don't add values for empty sub-resources + if (strlen($value)) { + $buffer .= "={$value}"; + } + } + } + } + + return $buffer; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/StorageObjectStore.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/StorageObjectStore.php new file mode 100644 index 0000000..a755138 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/StorageObjectStore.php @@ -0,0 +1,96 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\Storage\IStorage; + +/** + * Object store that wraps a storage backend, mostly for testing purposes + */ +class StorageObjectStore implements IObjectStore { + /** @var IStorage */ + private $storage; + + /** + * @param IStorage $storage + */ + public function __construct(IStorage $storage) { + $this->storage = $storage; + } + + /** + * @return string the container or bucket name where objects are stored + * @since 7.0.0 + */ + public function getStorageId() { + $this->storage->getId(); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return resource stream with the read data + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function readObject($urn) { + $handle = $this->storage->fopen($urn, 'r'); + if ($handle) { + return $handle; + } else { + throw new \Exception(); + } + } + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function writeObject($urn, $stream) { + $handle = $this->storage->fopen($urn, 'w'); + if ($handle) { + stream_copy_to_stream($stream, $handle); + fclose($handle); + } else { + throw new \Exception(); + } + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return void + * @throws \Exception when something goes wrong, message will be logged + * @since 7.0.0 + */ + public function deleteObject($urn) { + $this->storage->unlink($urn); + } + + public function objectExists($urn) { + return $this->storage->file_exists($urn); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Swift.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Swift.php new file mode 100644 index 0000000..5ee924c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/Swift.php @@ -0,0 +1,152 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\BadResponseException; +use function GuzzleHttp\Psr7\stream_for; +use Icewind\Streams\RetryWrapper; +use OCP\Files\NotFoundException; +use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\StorageAuthException; + +const SWIFT_SEGMENT_SIZE = 1073741824; // 1GB + +class Swift implements IObjectStore { + /** + * @var array + */ + private $params; + + /** @var SwiftFactory */ + private $swiftFactory; + + public function __construct($params, SwiftFactory $connectionFactory = null) { + $this->swiftFactory = $connectionFactory ?: new SwiftFactory( + \OC::$server->getMemCacheFactory()->createDistributed('swift::'), + $params, + \OC::$server->getLogger() + ); + $this->params = $params; + } + + /** + * @return \OpenStack\ObjectStore\v1\Models\Container + * @throws StorageAuthException + * @throws \OCP\Files\StorageNotAvailableException + */ + private function getContainer() { + return $this->swiftFactory->getContainer(); + } + + /** + * @return string the container name where objects are stored + */ + public function getStorageId() { + if (isset($this->params['bucket'])) { + return $this->params['bucket']; + } + + return $this->params['container']; + } + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @throws \Exception from openstack lib when something goes wrong + */ + public function writeObject($urn, $stream) { + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile('swiftwrite'); + file_put_contents($tmpFile, $stream); + $handle = fopen($tmpFile, 'rb'); + + if (filesize($tmpFile) < SWIFT_SEGMENT_SIZE) { + $this->getContainer()->createObject([ + 'name' => $urn, + 'stream' => stream_for($handle) + ]); + } else { + $this->getContainer()->createLargeObject([ + 'name' => $urn, + 'stream' => stream_for($handle), + 'segmentSize' => SWIFT_SEGMENT_SIZE + ]); + } + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return resource stream with the read data + * @throws \Exception from openstack or GuzzleHttp libs when something goes wrong + * @throws NotFoundException if file does not exist + */ + public function readObject($urn) { + try { + $publicUri = $this->getContainer()->getObject($urn)->getPublicUri(); + $tokenId = $this->swiftFactory->getCachedTokenId(); + + $response = (new Client())->request('GET', $publicUri, + [ + 'stream' => true, + 'headers' => [ + 'X-Auth-Token' => $tokenId, + 'Cache-Control' => 'no-cache' + ], + ] + ); + } catch (BadResponseException $e) { + if ($e->getResponse() && $e->getResponse()->getStatusCode() === 404) { + throw new NotFoundException("object $urn not found in object store"); + } else { + throw $e; + } + } + + return RetryWrapper::wrap($response->getBody()->detach()); + } + + /** + * @param string $urn Unified Resource Name + * @return void + * @throws \Exception from openstack lib when something goes wrong + */ + public function deleteObject($urn) { + $this->getContainer()->getObject($urn)->delete(); + } + + /** + * @return void + * @throws \Exception from openstack lib when something goes wrong + */ + public function deleteContainer() { + $this->getContainer()->delete(); + } + + public function objectExists($urn) { + return $this->getContainer()->objectExists($urn); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftFactory.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftFactory.php new file mode 100644 index 0000000..54975e8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftFactory.php @@ -0,0 +1,287 @@ + + * + * @author Adrian Brzezinski + * @author Christoph Wurst + * @author Julien Lutran + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Volker + * @author William Pain + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\HandlerStack; +use OCP\Files\StorageAuthException; +use OCP\Files\StorageNotAvailableException; +use OCP\ICache; +use OCP\ILogger; +use OpenStack\Common\Auth\Token; +use OpenStack\Common\Error\BadResponseError; +use OpenStack\Common\Transport\Utils as TransportUtils; +use OpenStack\Identity\v2\Models\Catalog; +use OpenStack\Identity\v2\Service as IdentityV2Service; +use OpenStack\Identity\v3\Service as IdentityV3Service; +use OpenStack\ObjectStore\v1\Models\Container; +use OpenStack\OpenStack; +use Psr\Http\Message\RequestInterface; + +class SwiftFactory { + private $cache; + private $params; + /** @var Container|null */ + private $container = null; + private $logger; + + public const DEFAULT_OPTIONS = [ + 'autocreate' => false, + 'urlType' => 'publicURL', + 'catalogName' => 'swift', + 'catalogType' => 'object-store' + ]; + + public function __construct(ICache $cache, array $params, ILogger $logger) { + $this->cache = $cache; + $this->params = $params; + $this->logger = $logger; + } + + /** + * Gets currently cached token id + * + * @return string + * @throws StorageAuthException + */ + public function getCachedTokenId() { + if (!isset($this->params['cachedToken'])) { + throw new StorageAuthException('Unauthenticated ObjectStore connection'); + } + + // Is it V2 token? + if (isset($this->params['cachedToken']['token'])) { + return $this->params['cachedToken']['token']['id']; + } + + return $this->params['cachedToken']['id']; + } + + private function getCachedToken(string $cacheKey) { + $cachedTokenString = $this->cache->get($cacheKey . '/token'); + if ($cachedTokenString) { + return json_decode($cachedTokenString, true); + } else { + return null; + } + } + + private function cacheToken(Token $token, string $serviceUrl, string $cacheKey) { + if ($token instanceof \OpenStack\Identity\v3\Models\Token) { + // for v3 the catalog is cached as part of the token, so no need to cache $serviceUrl separately + $value = $token->export(); + } else { + /** @var \OpenStack\Identity\v2\Models\Token $token */ + $value = [ + 'serviceUrl' => $serviceUrl, + 'token' => [ + 'issued_at' => $token->issuedAt->format('c'), + 'expires' => $token->expires->format('c'), + 'id' => $token->id, + 'tenant' => $token->tenant + ] + ]; + } + + $this->params['cachedToken'] = $value; + $this->cache->set($cacheKey . '/token', json_encode($value)); + } + + /** + * @return OpenStack + * @throws StorageAuthException + */ + private function getClient() { + if (isset($this->params['bucket'])) { + $this->params['container'] = $this->params['bucket']; + } + if (!isset($this->params['container'])) { + $this->params['container'] = 'nextcloud'; + } + if (isset($this->params['user']) && is_array($this->params['user'])) { + $userName = $this->params['user']['name']; + } else { + if (!isset($this->params['username']) && isset($this->params['user'])) { + $this->params['username'] = $this->params['user']; + } + $userName = $this->params['username']; + } + if (!isset($this->params['tenantName']) && isset($this->params['tenant'])) { + $this->params['tenantName'] = $this->params['tenant']; + } + if (isset($this->params['domain'])) { + $this->params['scope']['project']['name'] = $this->params['tenant']; + $this->params['scope']['project']['domain']['name'] = $this->params['domain']; + } + $this->params = array_merge(self::DEFAULT_OPTIONS, $this->params); + + $cacheKey = $userName . '@' . $this->params['url'] . '/' . $this->params['container']; + $token = $this->getCachedToken($cacheKey); + $this->params['cachedToken'] = $token; + + $httpClient = new Client([ + 'base_uri' => TransportUtils::normalizeUrl($this->params['url']), + 'handler' => HandlerStack::create() + ]); + + if (isset($this->params['user']) && is_array($this->params['user']) && isset($this->params['user']['name'])) { + if (!isset($this->params['scope'])) { + throw new StorageAuthException('Scope has to be defined for V3 requests'); + } + + return $this->auth(IdentityV3Service::factory($httpClient), $cacheKey); + } else { + return $this->auth(SwiftV2CachingAuthService::factory($httpClient), $cacheKey); + } + } + + /** + * @param IdentityV2Service|IdentityV3Service $authService + * @param string $cacheKey + * @return OpenStack + * @throws StorageAuthException + */ + private function auth($authService, string $cacheKey) { + $this->params['identityService'] = $authService; + $this->params['authUrl'] = $this->params['url']; + + $cachedToken = $this->params['cachedToken']; + $hasValidCachedToken = false; + if (\is_array($cachedToken)) { + if ($authService instanceof IdentityV3Service) { + $token = $authService->generateTokenFromCache($cachedToken); + if (\is_null($token->catalog)) { + $this->logger->warning('Invalid cached token for swift, no catalog set: ' . json_encode($cachedToken)); + } elseif ($token->hasExpired()) { + $this->logger->debug('Cached token for swift expired'); + } else { + $hasValidCachedToken = true; + } + } else { + try { + /** @var \OpenStack\Identity\v2\Models\Token $token */ + $token = $authService->model(\OpenStack\Identity\v2\Models\Token::class, $cachedToken['token']); + $now = new \DateTimeImmutable("now"); + if ($token->expires > $now) { + $hasValidCachedToken = true; + $this->params['v2cachedToken'] = $token; + $this->params['v2serviceUrl'] = $cachedToken['serviceUrl']; + } else { + $this->logger->debug('Cached token for swift expired'); + } + } catch (\Exception $e) { + $this->logger->logException($e); + } + } + } + + if (!$hasValidCachedToken) { + unset($this->params['cachedToken']); + try { + list($token, $serviceUrl) = $authService->authenticate($this->params); + $this->cacheToken($token, $serviceUrl, $cacheKey); + } catch (ConnectException $e) { + throw new StorageAuthException('Failed to connect to keystone, verify the keystone url', $e); + } catch (ClientException $e) { + $statusCode = $e->getResponse()->getStatusCode(); + if ($statusCode === 404) { + throw new StorageAuthException('Keystone not found, verify the keystone url', $e); + } elseif ($statusCode === 412) { + throw new StorageAuthException('Precondition failed, verify the keystone url', $e); + } elseif ($statusCode === 401) { + throw new StorageAuthException('Authentication failed, verify the username, password and possibly tenant', $e); + } else { + throw new StorageAuthException('Unknown error', $e); + } + } catch (RequestException $e) { + throw new StorageAuthException('Connection reset while connecting to keystone, verify the keystone url', $e); + } + } + + + $client = new OpenStack($this->params); + + return $client; + } + + /** + * @return \OpenStack\ObjectStore\v1\Models\Container + * @throws StorageAuthException + * @throws StorageNotAvailableException + */ + public function getContainer() { + if (is_null($this->container)) { + $this->container = $this->createContainer(); + } + + return $this->container; + } + + /** + * @return \OpenStack\ObjectStore\v1\Models\Container + * @throws StorageAuthException + * @throws StorageNotAvailableException + */ + private function createContainer() { + $client = $this->getClient(); + $objectStoreService = $client->objectStoreV1(); + + $autoCreate = isset($this->params['autocreate']) && $this->params['autocreate'] === true; + try { + $container = $objectStoreService->getContainer($this->params['container']); + if ($autoCreate) { + $container->getMetadata(); + } + return $container; + } catch (BadResponseError $ex) { + // if the container does not exist and autocreate is true try to create the container on the fly + if ($ex->getResponse()->getStatusCode() === 404 && $autoCreate) { + return $objectStoreService->createContainer([ + 'name' => $this->params['container'] + ]); + } else { + throw new StorageNotAvailableException('Invalid response while trying to get container info', StorageNotAvailableException::STATUS_ERROR, $ex); + } + } catch (ConnectException $e) { + /** @var RequestInterface $request */ + $request = $e->getRequest(); + $host = $request->getUri()->getHost() . ':' . $request->getUri()->getPort(); + \OC::$server->getLogger()->error("Can't connect to object storage server at $host"); + throw new StorageNotAvailableException("Can't connect to object storage server at $host", StorageNotAvailableException::STATUS_ERROR, $e); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftV2CachingAuthService.php b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftV2CachingAuthService.php new file mode 100644 index 0000000..635d090 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/ObjectStore/SwiftV2CachingAuthService.php @@ -0,0 +1,39 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\ObjectStore; + +use OpenStack\Identity\v2\Service; + +class SwiftV2CachingAuthService extends Service { + public function authenticate(array $options = []): array { + if (!empty($options['v2cachedToken'])) { + return [$options['v2cachedToken'], $options['v2serviceUrl']]; + } else { + return parent::authenticate($options); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchBinaryOperator.php b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchBinaryOperator.php new file mode 100644 index 0000000..523ac0e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchBinaryOperator.php @@ -0,0 +1,59 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchOperator; + +class SearchBinaryOperator implements ISearchBinaryOperator { + /** @var string */ + private $type; + /** @var ISearchOperator[] */ + private $arguments; + + /** + * SearchBinaryOperator constructor. + * + * @param string $type + * @param ISearchOperator[] $arguments + */ + public function __construct($type, array $arguments) { + $this->type = $type; + $this->arguments = $arguments; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return ISearchOperator[] + */ + public function getArguments() { + return $this->arguments; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchComparison.php b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchComparison.php new file mode 100644 index 0000000..32a1881 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchComparison.php @@ -0,0 +1,69 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchComparison; + +class SearchComparison implements ISearchComparison { + /** @var string */ + private $type; + /** @var string */ + private $field; + /** @var string|integer|\DateTime */ + private $value; + + /** + * SearchComparison constructor. + * + * @param string $type + * @param string $field + * @param \DateTime|int|string $value + */ + public function __construct($type, $field, $value) { + $this->type = $type; + $this->field = $field; + $this->value = $value; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } + + /** + * @return \DateTime|int|string + */ + public function getValue() { + return $this->value; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchOrder.php b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchOrder.php new file mode 100644 index 0000000..4bff8ba --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchOrder.php @@ -0,0 +1,58 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchOrder; + +class SearchOrder implements ISearchOrder { + /** @var string */ + private $direction; + /** @var string */ + private $field; + + /** + * SearchOrder constructor. + * + * @param string $direction + * @param string $field + */ + public function __construct($direction, $field) { + $this->direction = $direction; + $this->field = $field; + } + + /** + * @return string + */ + public function getDirection() { + return $this->direction; + } + + /** + * @return string + */ + public function getField() { + return $this->field; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchQuery.php b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchQuery.php new file mode 100644 index 0000000..b7b8b80 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Search/SearchQuery.php @@ -0,0 +1,108 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Search; + +use OCP\Files\Search\ISearchOperator; +use OCP\Files\Search\ISearchOrder; +use OCP\Files\Search\ISearchQuery; +use OCP\IUser; + +class SearchQuery implements ISearchQuery { + /** @var ISearchOperator */ + private $searchOperation; + /** @var integer */ + private $limit; + /** @var integer */ + private $offset; + /** @var ISearchOrder[] */ + private $order; + /** @var IUser */ + private $user; + private $limitToHome; + + /** + * SearchQuery constructor. + * + * @param ISearchOperator $searchOperation + * @param int $limit + * @param int $offset + * @param array $order + * @param IUser $user + * @param bool $limitToHome + */ + public function __construct( + ISearchOperator $searchOperation, + int $limit, + int $offset, + array $order, + IUser $user, + bool $limitToHome = false + ) { + $this->searchOperation = $searchOperation; + $this->limit = $limit; + $this->offset = $offset; + $this->order = $order; + $this->user = $user; + $this->limitToHome = $limitToHome; + } + + /** + * @return ISearchOperator + */ + public function getSearchOperation() { + return $this->searchOperation; + } + + /** + * @return int + */ + public function getLimit() { + return $this->limit; + } + + /** + * @return int + */ + public function getOffset() { + return $this->offset; + } + + /** + * @return ISearchOrder[] + */ + public function getOrder() { + return $this->order; + } + + /** + * @return IUser + */ + public function getUser() { + return $this->user; + } + + public function limitToHome(): bool { + return $this->limitToHome; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/NewSimpleFile.php b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/NewSimpleFile.php new file mode 100644 index 0000000..b8e5094 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/NewSimpleFile.php @@ -0,0 +1,226 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\SimpleFS; + +use Icewind\Streams\CallbackWrapper; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; + +class NewSimpleFile implements ISimpleFile { + private $parentFolder; + private $name; + /** @var File|null */ + private $file = null; + + /** + * File constructor. + * + * @param File $file + */ + public function __construct(Folder $parentFolder, string $name) { + $this->parentFolder = $parentFolder; + $this->name = $name; + } + + /** + * Get the name + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Get the size in bytes + * + * @return int + */ + public function getSize() { + if ($this->file) { + return $this->file->getSize(); + } else { + return 0; + } + } + + /** + * Get the ETag + * + * @return string + */ + public function getETag() { + if ($this->file) { + return $this->file->getEtag(); + } else { + return ''; + } + } + + /** + * Get the last modification time + * + * @return int + */ + public function getMTime() { + if ($this->file) { + return $this->file->getMTime(); + } else { + return time(); + } + } + + /** + * Get the content + * + * @return string + * @throws NotFoundException + * @throws NotPermittedException + */ + public function getContent() { + if ($this->file) { + $result = $this->file->getContent(); + + if ($result === false) { + $this->checkFile(); + } + + return $result; + } else { + return ''; + } + } + + /** + * Overwrite the file + * + * @param string|resource $data + * @throws NotPermittedException + * @throws NotFoundException + */ + public function putContent($data) { + try { + if ($this->file) { + $this->file->putContent($data); + } else { + $this->file = $this->parentFolder->newFile($this->name, $data); + } + } catch (NotFoundException $e) { + $this->checkFile(); + } + } + + /** + * Sometimes there are some issues with the AppData. Most of them are from + * user error. But we should handle them gracefull anyway. + * + * If for some reason the current file can't be found. We remove it. + * Then traverse up and check all folders if they exists. This so that the + * next request will have a valid appdata structure again. + * + * @throws NotFoundException + */ + private function checkFile() { + $cur = $this->file; + + while ($cur->stat() === false) { + $parent = $cur->getParent(); + try { + $cur->delete(); + } catch (NotFoundException $e) { + // Just continue then + } + $cur = $parent; + } + + if ($cur !== $this->file) { + throw new NotFoundException('File does not exist'); + } + } + + + /** + * Delete the file + * + * @throws NotPermittedException + */ + public function delete() { + if ($this->file) { + $this->file->delete(); + } + } + + /** + * Get the MimeType + * + * @return string + */ + public function getMimeType() { + if ($this->file) { + return $this->file->getMimeType(); + } else { + return 'text/plain'; + } + } + + /** + * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen + * + * @return resource + * @throws \OCP\Files\NotPermittedException + * @since 14.0.0 + */ + public function read() { + if ($this->file) { + return $this->file->fopen('r'); + } else { + return fopen('php://temp', 'r'); + } + } + + /** + * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen + * + * @return resource + * @throws \OCP\Files\NotPermittedException + * @since 14.0.0 + */ + public function write() { + if ($this->file) { + return $this->file->fopen('w'); + } else { + $source = fopen('php://temp', 'w+'); + return CallbackWrapper::wrap($source, null, null, null, null, function () use ($source) { + rewind($source); + $this->putContent($source); + }); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFile.php b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFile.php new file mode 100644 index 0000000..108f53e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFile.php @@ -0,0 +1,183 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\SimpleFS; + +use OCP\Files\File; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; + +class SimpleFile implements ISimpleFile { + + /** @var File $file */ + private $file; + + /** + * File constructor. + * + * @param File $file + */ + public function __construct(File $file) { + $this->file = $file; + } + + /** + * Get the name + * + * @return string + */ + public function getName() { + return $this->file->getName(); + } + + /** + * Get the size in bytes + * + * @return int + */ + public function getSize() { + return $this->file->getSize(); + } + + /** + * Get the ETag + * + * @return string + */ + public function getETag() { + return $this->file->getEtag(); + } + + /** + * Get the last modification time + * + * @return int + */ + public function getMTime() { + return $this->file->getMTime(); + } + + /** + * Get the content + * + * @throws NotPermittedException + * @throws NotFoundException + * @return string + */ + public function getContent() { + $result = $this->file->getContent(); + + if ($result === false) { + $this->checkFile(); + } + + return $result; + } + + /** + * Overwrite the file + * + * @param string|resource $data + * @throws NotPermittedException + * @throws NotFoundException + */ + public function putContent($data) { + try { + return $this->file->putContent($data); + } catch (NotFoundException $e) { + $this->checkFile(); + } + } + + /** + * Sometimes there are some issues with the AppData. Most of them are from + * user error. But we should handle them gracefull anyway. + * + * If for some reason the current file can't be found. We remove it. + * Then traverse up and check all folders if they exists. This so that the + * next request will have a valid appdata structure again. + * + * @throws NotFoundException + */ + private function checkFile() { + $cur = $this->file; + + while ($cur->stat() === false) { + $parent = $cur->getParent(); + try { + $cur->delete(); + } catch (NotFoundException $e) { + // Just continue then + } + $cur = $parent; + } + + if ($cur !== $this->file) { + throw new NotFoundException('File does not exist'); + } + } + + + /** + * Delete the file + * + * @throws NotPermittedException + */ + public function delete() { + $this->file->delete(); + } + + /** + * Get the MimeType + * + * @return string + */ + public function getMimeType() { + return $this->file->getMimeType(); + } + + /** + * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen + * + * @return resource + * @throws \OCP\Files\NotPermittedException + * @since 14.0.0 + */ + public function read() { + return $this->file->fopen('r'); + } + + /** + * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen + * + * @return resource + * @throws \OCP\Files\NotPermittedException + * @since 14.0.0 + */ + public function write() { + return $this->file->fopen('w'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFolder.php b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFolder.php new file mode 100644 index 0000000..dc8d655 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/SimpleFS/SimpleFolder.php @@ -0,0 +1,94 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\SimpleFS; + +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFolder; + +class SimpleFolder implements ISimpleFolder { + + /** @var Folder */ + private $folder; + + /** + * Folder constructor. + * + * @param Folder $folder + */ + public function __construct(Folder $folder) { + $this->folder = $folder; + } + + public function getName() { + return $this->folder->getName(); + } + + public function getDirectoryListing() { + $listing = $this->folder->getDirectoryListing(); + + $fileListing = array_map(function (Node $file) { + if ($file instanceof File) { + return new SimpleFile($file); + } + return null; + }, $listing); + + $fileListing = array_filter($fileListing); + + return array_values($fileListing); + } + + public function delete() { + $this->folder->delete(); + } + + public function fileExists($name) { + return $this->folder->nodeExists($name); + } + + public function getFile($name) { + $file = $this->folder->get($name); + + if (!($file instanceof File)) { + throw new NotFoundException(); + } + + return new SimpleFile($file); + } + + public function newFile($name, $content = null) { + if ($content === null) { + // delay creating the file until it's written to + return new NewSimpleFile($this->folder, $name); + } else { + $file = $this->folder->newFile($name, $content); + return new SimpleFile($file); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Common.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Common.php new file mode 100644 index 0000000..cba8afa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Common.php @@ -0,0 +1,891 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Greta Doci + * @author hkjolhede + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Roland Tapken + * @author Sam Tuke + * @author scambra + * @author Stefan Weil + * @author Thomas Müller + * @author Vincent Petry + * @author Vinicius Cubas Brand + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\Propagator; +use OC\Files\Cache\Scanner; +use OC\Files\Cache\Updater; +use OC\Files\Cache\Watcher; +use OC\Files\Filesystem; +use OC\Files\Storage\Wrapper\Jail; +use OC\Files\Storage\Wrapper\Wrapper; +use OCP\Files\EmptyFileNameException; +use OCP\Files\FileNameTooLongException; +use OCP\Files\GenericFileException; +use OCP\Files\InvalidCharacterInPathException; +use OCP\Files\InvalidDirectoryException; +use OCP\Files\InvalidPathException; +use OCP\Files\ReservedWordException; +use OCP\Files\Storage\ILockingStorage; +use OCP\Files\Storage\IStorage; +use OCP\Files\Storage\IWriteStreamStorage; +use OCP\ILogger; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * Some \OC\Files\Storage\Common methods call functions which are first defined + * in classes which extend it, e.g. $this->stat() . + */ +abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { + use LocalTempFileTrait; + + protected $cache; + protected $scanner; + protected $watcher; + protected $propagator; + protected $storageCache; + protected $updater; + + protected $mountOptions = []; + protected $owner = null; + + private $shouldLogLocks = null; + private $logger; + + public function __construct($parameters) { + } + + /** + * Remove a file or folder + * + * @param string $path + * @return bool + */ + protected function remove($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } elseif ($this->is_file($path)) { + return $this->unlink($path); + } else { + return false; + } + } + + public function is_dir($path) { + return $this->filetype($path) === 'dir'; + } + + public function is_file($path) { + return $this->filetype($path) === 'file'; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; //by definition + } else { + $stat = $this->stat($path); + if (isset($stat['size'])) { + return $stat['size']; + } else { + return 0; + } + } + } + + public function isReadable($path) { + // at least check whether it exists + // subclasses might want to implement this more thoroughly + return $this->file_exists($path); + } + + public function isUpdatable($path) { + // at least check whether it exists + // subclasses might want to implement this more thoroughly + // a non-existing file/folder isn't updatable + return $this->file_exists($path); + } + + public function isCreatable($path) { + if ($this->is_dir($path) && $this->isUpdatable($path)) { + return true; + } + return false; + } + + public function isDeletable($path) { + if ($path === '' || $path === '/') { + return false; + } + $parent = dirname($path); + return $this->isUpdatable($parent) && $this->isUpdatable($path); + } + + public function isSharable($path) { + return $this->isReadable($path); + } + + public function getPermissions($path) { + $permissions = 0; + if ($this->isCreatable($path)) { + $permissions |= \OCP\Constants::PERMISSION_CREATE; + } + if ($this->isReadable($path)) { + $permissions |= \OCP\Constants::PERMISSION_READ; + } + if ($this->isUpdatable($path)) { + $permissions |= \OCP\Constants::PERMISSION_UPDATE; + } + if ($this->isDeletable($path)) { + $permissions |= \OCP\Constants::PERMISSION_DELETE; + } + if ($this->isSharable($path)) { + $permissions |= \OCP\Constants::PERMISSION_SHARE; + } + return $permissions; + } + + public function filemtime($path) { + $stat = $this->stat($path); + if (isset($stat['mtime']) && $stat['mtime'] > 0) { + return $stat['mtime']; + } else { + return 0; + } + } + + public function file_get_contents($path) { + $handle = $this->fopen($path, "r"); + if (!$handle) { + return false; + } + $data = stream_get_contents($handle); + fclose($handle); + return $data; + } + + public function file_put_contents($path, $data) { + $handle = $this->fopen($path, "w"); + $this->removeCachedFile($path); + $count = fwrite($handle, $data); + fclose($handle); + return $count; + } + + public function rename($path1, $path2) { + $this->remove($path2); + + $this->removeCachedFile($path1); + return $this->copy($path1, $path2) and $this->remove($path1); + } + + public function copy($path1, $path2) { + if ($this->is_dir($path1)) { + $this->remove($path2); + $dir = $this->opendir($path1); + $this->mkdir($path2); + while ($file = readdir($dir)) { + if (!Filesystem::isIgnoredDir($file)) { + if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) { + return false; + } + } + } + closedir($dir); + return true; + } else { + $source = $this->fopen($path1, 'r'); + $target = $this->fopen($path2, 'w'); + [, $result] = \OC_Helper::streamCopy($source, $target); + if (!$result) { + \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2"); + } + $this->removeCachedFile($path2); + return $result; + } + } + + public function getMimeType($path) { + if ($this->is_dir($path)) { + return 'httpd/unix-directory'; + } elseif ($this->file_exists($path)) { + return \OC::$server->getMimeTypeDetector()->detectPath($path); + } else { + return false; + } + } + + public function hash($type, $path, $raw = false) { + $fh = $this->fopen($path, 'rb'); + $ctx = hash_init($type); + hash_update_stream($ctx, $fh); + fclose($fh); + return hash_final($ctx, $raw); + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->getCachedFile($path); + } + + /** + * @param string $path + * @param string $target + */ + private function addLocalFolder($path, $target) { + $dh = $this->opendir($path); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->is_dir($path . '/' . $file)) { + mkdir($target . '/' . $file); + $this->addLocalFolder($path . '/' . $file, $target . '/' . $file); + } else { + $tmp = $this->toTmpFile($path . '/' . $file); + rename($tmp, $target . '/' . $file); + } + } + } + } + } + + /** + * @param string $query + * @param string $dir + * @return array + */ + protected function searchInDir($query, $dir = '') { + $files = []; + $dh = $this->opendir($dir); + if (is_resource($dh)) { + while (($item = readdir($dh)) !== false) { + if (\OC\Files\Filesystem::isIgnoredDir($item)) { + continue; + } + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if ($this->is_dir($dir . '/' . $item)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + } + closedir($dh); + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking + * the mtime should always return false here. As a result storage implementations that always return false expect + * exclusive access to the backend and will not pick up files that have been added in a way that circumvents + * ownClouds filesystem. + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path) > $time; + } + + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->cache)) { + $storage->cache = new Cache($storage); + } + return $storage->cache; + } + + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->scanner)) { + $storage->scanner = new Scanner($storage); + } + return $storage->scanner; + } + + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->watcher)) { + $this->watcher = new Watcher($storage); + $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER); + $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy)); + } + return $this->watcher; + } + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->propagator)) { + $config = \OC::$server->getSystemConfig(); + $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]); + } + return $storage->propagator; + } + + public function getUpdater($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->updater)) { + $storage->updater = new Updater($storage); + } + return $storage->updater; + } + + public function getStorageCache($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->storageCache)) { + $this->storageCache = new \OC\Files\Cache\Storage($storage); + } + return $this->storageCache; + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return string|false uid or false + */ + public function getOwner($path) { + if ($this->owner === null) { + $this->owner = \OC_User::getUser(); + } + + return $this->owner; + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return uniqid(); + } + + /** + * clean a path, i.e. remove all redundant '.' and '..' + * making sure that it can't point to higher than '/' + * + * @param string $path The path to clean + * @return string cleaned path + */ + public function cleanPath($path) { + if (strlen($path) == 0 or $path[0] != '/') { + $path = '/' . $path; + } + + $output = []; + foreach (explode('/', $path) as $chunk) { + if ($chunk == '..') { + array_pop($output); + } elseif ($chunk == '.') { + } else { + $output[] = $chunk; + } + } + return implode('/', $output); + } + + /** + * Test a storage for availability + * + * @return bool + */ + public function test() { + try { + if ($this->stat('')) { + return true; + } + \OC::$server->getLogger()->info("External storage not available: stat() failed"); + return false; + } catch (\Exception $e) { + \OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage()); + \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]); + return false; + } + } + + /** + * get the free space in the storage + * + * @param string $path + * @return int|false + */ + public function free_space($path) { + return \OCP\Files\FileInfo::SPACE_UNKNOWN; + } + + /** + * {@inheritdoc} + */ + public function isLocal() { + // the common implementation returns a temporary file by + // default, which is not local + return false; + } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } + return is_a($this, $class); + } + + /** + * A custom storage implementation can return an url for direct download of a give file. + * + * For now the returned array can hold the parameter url - in future more attributes might follow. + * + * @param string $path + * @return array|false + */ + public function getDirectDownload($path) { + return []; + } + + /** + * @inheritdoc + * @throws InvalidPathException + */ + public function verifyPath($path, $fileName) { + + // verify empty and dot files + $trimmed = trim($fileName); + if ($trimmed === '') { + throw new EmptyFileNameException(); + } + + if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { + throw new InvalidDirectoryException(); + } + + if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) { + // verify database - e.g. mysql only 3-byte chars + if (preg_match('%(?: + \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +)%xs', $fileName)) { + throw new InvalidCharacterInPathException(); + } + } + + // 255 characters is the limit on common file systems (ext/xfs) + // oc_filecache has a 250 char length limit for the filename + if (isset($fileName[250])) { + throw new FileNameTooLongException(); + } + + // NOTE: $path will remain unverified for now + $this->verifyPosixPath($fileName); + } + + /** + * @param string $fileName + * @throws InvalidPathException + */ + protected function verifyPosixPath($fileName) { + $fileName = trim($fileName); + $this->scanForInvalidCharacters($fileName, "\\/"); + $reservedNames = ['*']; + if (in_array($fileName, $reservedNames)) { + throw new ReservedWordException(); + } + } + + /** + * @param string $fileName + * @param string $invalidChars + * @throws InvalidPathException + */ + private function scanForInvalidCharacters($fileName, $invalidChars) { + foreach (str_split($invalidChars) as $char) { + if (strpos($fileName, $char) !== false) { + throw new InvalidCharacterInPathException(); + } + } + + $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); + if ($sanitizedFileName !== $fileName) { + throw new InvalidCharacterInPathException(); + } + } + + /** + * @param array $options + */ + public function setMountOptions(array $options) { + $this->mountOptions = $options; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getMountOption($name, $default = null) { + return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + + if ($sourceStorage->is_dir($sourceInternalPath)) { + $dh = $sourceStorage->opendir($sourceInternalPath); + $result = $this->mkdir($targetInternalPath); + if (is_resource($dh)) { + while ($result and ($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file); + } + } + } + } else { + $source = $sourceStorage->fopen($sourceInternalPath, 'r'); + $result = false; + if ($source) { + try { + $this->writeStream($targetInternalPath, $source); + $result = true; + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']); + } + } + + if ($result and $preserveMtime) { + $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); + } + + if (!$result) { + // delete partially written target file + $this->unlink($targetInternalPath); + // delete cache entry that was created by fopen + $this->getCache()->remove($targetInternalPath); + } + } + return (bool)$result; + } + + /** + * Check if a storage is the same as the current one, including wrapped storages + * + * @param IStorage $storage + * @return bool + */ + private function isSameStorage(IStorage $storage): bool { + while ($storage->instanceOfStorage(Wrapper::class)) { + /** + * @var Wrapper $sourceStorage + */ + $storage = $storage->getWrapperStorage(); + } + + return $storage === $this; + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($this->isSameStorage($sourceStorage)) { + // resolve any jailed paths + while ($sourceStorage->instanceOfStorage(Jail::class)) { + /** + * @var Jail $sourceStorage + */ + $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); + $sourceStorage = $sourceStorage->getUnjailedStorage(); + } + + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + if (!$sourceStorage->isDeletable($sourceInternalPath)) { + return false; + } + + $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true); + if ($result) { + if ($sourceStorage->is_dir($sourceInternalPath)) { + $result &= $sourceStorage->rmdir($sourceInternalPath); + } else { + $result &= $sourceStorage->unlink($sourceInternalPath); + } + } + return $result; + } + + /** + * @inheritdoc + */ + public function getMetaData($path) { + $permissions = $this->getPermissions($path); + if (!$permissions & \OCP\Constants::PERMISSION_READ) { + //can't read, nothing we can do + return null; + } + + $data = []; + $data['mimetype'] = $this->getMimeType($path); + $data['mtime'] = $this->filemtime($path); + if ($data['mtime'] === false) { + $data['mtime'] = time(); + } + if ($data['mimetype'] == 'httpd/unix-directory') { + $data['size'] = -1; //unknown + } else { + $data['size'] = $this->filesize($path); + } + $data['etag'] = $this->getETag($path); + $data['storage_mtime'] = $data['mtime']; + $data['permissions'] = $permissions; + $data['name'] = basename($path); + + return $data; + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + $logger = $this->getLockLogger(); + if ($logger) { + $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; + $logger->info( + sprintf( + 'acquire %s lock on "%s" on storage "%s"', + $typeString, + $path, + $this->getId() + ), + [ + 'app' => 'locking', + ] + ); + } + try { + $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path); + } catch (LockedException $e) { + if ($logger) { + $logger->logException($e, ['level' => ILogger::INFO]); + } + throw $e; + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + $logger = $this->getLockLogger(); + if ($logger) { + $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; + $logger->info( + sprintf( + 'release %s lock on "%s" on storage "%s"', + $typeString, + $path, + $this->getId() + ), + [ + 'app' => 'locking', + ] + ); + } + try { + $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); + } catch (LockedException $e) { + if ($logger) { + $logger->logException($e, ['level' => ILogger::INFO]); + } + throw $e; + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function changeLock($path, $type, ILockingProvider $provider) { + $logger = $this->getLockLogger(); + if ($logger) { + $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; + $logger->info( + sprintf( + 'change lock on "%s" to %s on storage "%s"', + $path, + $typeString, + $this->getId() + ), + [ + 'app' => 'locking', + ] + ); + } + try { + $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); + } catch (LockedException $e) { + \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]); + throw $e; + } + } + + private function getLockLogger() { + if (is_null($this->shouldLogLocks)) { + $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false); + $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null; + } + return $this->logger; + } + + /** + * @return array [ available, last_checked ] + */ + public function getAvailability() { + return $this->getStorageCache()->getAvailability(); + } + + /** + * @param bool $isAvailable + */ + public function setAvailability($isAvailable) { + $this->getStorageCache()->setAvailability($isAvailable); + } + + /** + * @return bool + */ + public function needsPartFile() { + return true; + } + + /** + * fallback implementation + * + * @param string $path + * @param resource $stream + * @param int $size + * @return int + */ + public function writeStream(string $path, $stream, int $size = null): int { + $target = $this->fopen($path, 'w'); + if (!$target) { + throw new GenericFileException("Failed to open $path for writing"); + } + try { + [$count, $result] = \OC_Helper::streamCopy($stream, $target); + if (!$result) { + throw new GenericFileException("Failed to copy stream"); + } + } finally { + fclose($target); + fclose($stream); + } + return $count; + } + + public function getDirectoryContent($directory): \Traversable { + $dh = $this->opendir($directory); + if (is_resource($dh)) { + $basePath = rtrim($directory, '/'); + while (($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) { + $childPath = $basePath . '/' . trim($file, '/'); + $metadata = $this->getMetaData($childPath); + if ($metadata !== null) { + yield $metadata; + } + } + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/CommonTest.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/CommonTest.php new file mode 100644 index 0000000..b590908 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/CommonTest.php @@ -0,0 +1,86 @@ + + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Felix Moeller + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local + */ + +namespace OC\Files\Storage; + +class CommonTest extends \OC\Files\Storage\Common { + /** + * underlying local storage used for missing functions + * @var \OC\Files\Storage\Local + */ + private $storage; + + public function __construct($params) { + $this->storage=new \OC\Files\Storage\Local($params); + } + + public function getId() { + return 'test::'.$this->storage->getId(); + } + public function mkdir($path) { + return $this->storage->mkdir($path); + } + public function rmdir($path) { + return $this->storage->rmdir($path); + } + public function opendir($path) { + return $this->storage->opendir($path); + } + public function stat($path) { + return $this->storage->stat($path); + } + public function filetype($path) { + return @$this->storage->filetype($path); + } + public function isReadable($path) { + return $this->storage->isReadable($path); + } + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + public function file_exists($path) { + return $this->storage->file_exists($path); + } + public function unlink($path) { + return $this->storage->unlink($path); + } + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + public function free_space($path) { + return $this->storage->free_space($path); + } + public function touch($path, $mtime=null) { + return $this->storage->touch($path, $mtime); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/DAV.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/DAV.php new file mode 100644 index 0000000..a6cfd77 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/DAV.php @@ -0,0 +1,879 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Carlos Cerrillo + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use Exception; +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\IteratorDirectory; +use OC\Files\Filesystem; +use OC\MemCache\ArrayCache; +use OCP\AppFramework\Http; +use OCP\Constants; +use OCP\Files\FileInfo; +use OCP\Files\ForbiddenException; +use OCP\Files\StorageInvalidException; +use OCP\Files\StorageNotAvailableException; +use OCP\Http\Client\IClientService; +use OCP\ICertificateManager; +use OCP\ILogger; +use OCP\Util; +use Psr\Http\Message\ResponseInterface; +use Sabre\DAV\Client; +use Sabre\DAV\Xml\Property\ResourceType; +use Sabre\HTTP\ClientException; +use Sabre\HTTP\ClientHttpException; + +/** + * Class DAV + * + * @package OC\Files\Storage + */ +class DAV extends Common { + /** @var string */ + protected $password; + /** @var string */ + protected $user; + /** @var string */ + protected $authType; + /** @var string */ + protected $host; + /** @var bool */ + protected $secure; + /** @var string */ + protected $root; + /** @var string */ + protected $certPath; + /** @var bool */ + protected $ready; + /** @var Client */ + protected $client; + /** @var ArrayCache */ + protected $statCache; + /** @var IClientService */ + protected $httpClientService; + /** @var ICertificateManager */ + protected $certManager; + + /** + * @param array $params + * @throws \Exception + */ + public function __construct($params) { + $this->statCache = new ArrayCache(); + $this->httpClientService = \OC::$server->getHTTPClientService(); + if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { + $host = $params['host']; + //remove leading http[s], will be generated in createBaseUri() + if (substr($host, 0, 8) == "https://") { + $host = substr($host, 8); + } elseif (substr($host, 0, 7) == "http://") { + $host = substr($host, 7); + } + $this->host = $host; + $this->user = $params['user']; + $this->password = $params['password']; + if (isset($params['authType'])) { + $this->authType = $params['authType']; + } + if (isset($params['secure'])) { + if (is_string($params['secure'])) { + $this->secure = ($params['secure'] === 'true'); + } else { + $this->secure = (bool)$params['secure']; + } + } else { + $this->secure = false; + } + if ($this->secure === true) { + // inject mock for testing + $this->certManager = \OC::$server->getCertificateManager(); + if (is_null($this->certManager)) { //no user + $this->certManager = \OC::$server->getCertificateManager(null); + } + } + $this->root = $params['root'] ?? '/'; + $this->root = '/' . ltrim($this->root, '/'); + $this->root = rtrim($this->root, '/') . '/'; + } else { + throw new \Exception('Invalid webdav storage configuration'); + } + } + + protected function init() { + if ($this->ready) { + return; + } + $this->ready = true; + + $settings = [ + 'baseUri' => $this->createBaseUri(), + 'userName' => $this->user, + 'password' => $this->password, + ]; + if (isset($this->authType)) { + $settings['authType'] = $this->authType; + } + + $proxy = \OC::$server->getConfig()->getSystemValue('proxy', ''); + if ($proxy !== '') { + $settings['proxy'] = $proxy; + } + + $this->client = new Client($settings); + $this->client->setThrowExceptions(true); + + if ($this->secure === true) { + $certPath = $this->certManager->getAbsoluteBundlePath(); + if (file_exists($certPath)) { + $this->certPath = $certPath; + } + if ($this->certPath) { + $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); + } + } + } + + /** + * Clear the stat cache + */ + public function clearStatCache() { + $this->statCache->clear(); + } + + /** {@inheritdoc} */ + public function getId() { + return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root; + } + + /** {@inheritdoc} */ + public function createBaseUri() { + $baseUri = 'http'; + if ($this->secure) { + $baseUri .= 's'; + } + $baseUri .= '://' . $this->host . $this->root; + return $baseUri; + } + + /** {@inheritdoc} */ + public function mkdir($path) { + $this->init(); + $path = $this->cleanPath($path); + $result = $this->simpleResponse('MKCOL', $path, null, 201); + if ($result) { + $this->statCache->set($path, true); + } + return $result; + } + + /** {@inheritdoc} */ + public function rmdir($path) { + $this->init(); + $path = $this->cleanPath($path); + // FIXME: some WebDAV impl return 403 when trying to DELETE + // a non-empty folder + $result = $this->simpleResponse('DELETE', $path . '/', null, 204); + $this->statCache->clear($path . '/'); + $this->statCache->remove($path); + return $result; + } + + /** {@inheritdoc} */ + public function opendir($path) { + $this->init(); + $path = $this->cleanPath($path); + try { + $response = $this->client->propFind( + $this->encodePath($path), + ['{DAV:}getetag'], + 1 + ); + if ($response === false) { + return false; + } + $content = []; + $files = array_keys($response); + array_shift($files); //the first entry is the current directory + + if (!$this->statCache->hasKey($path)) { + $this->statCache->set($path, true); + } + foreach ($files as $file) { + $file = urldecode($file); + // do not store the real entry, we might not have all properties + if (!$this->statCache->hasKey($path)) { + $this->statCache->set($file, true); + } + $file = basename($file); + $content[] = $file; + } + return IteratorDirectory::wrap($content); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** + * Propfind call with cache handling. + * + * First checks if information is cached. + * If not, request it from the server then store to cache. + * + * @param string $path path to propfind + * + * @return array|boolean propfind response or false if the entry was not found + * + * @throws ClientHttpException + */ + protected function propfind($path) { + $path = $this->cleanPath($path); + $cachedResponse = $this->statCache->get($path); + // we either don't know it, or we know it exists but need more details + if (is_null($cachedResponse) || $cachedResponse === true) { + $this->init(); + try { + $response = $this->client->propFind( + $this->encodePath($path), + [ + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}getcontenttype', + '{http://owncloud.org/ns}permissions', + '{http://open-collaboration-services.org/ns}share-permissions', + '{DAV:}resourcetype', + '{DAV:}getetag', + ] + ); + $this->statCache->set($path, $response); + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) { + $this->statCache->clear($path . '/'); + $this->statCache->set($path, false); + return false; + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + } else { + $response = $cachedResponse; + } + return $response; + } + + /** {@inheritdoc} */ + public function filetype($path) { + try { + $response = $this->propfind($path); + if ($response === false) { + return false; + } + $responseType = []; + if (isset($response["{DAV:}resourcetype"])) { + /** @var ResourceType[] $response */ + $responseType = $response["{DAV:}resourcetype"]->getValue(); + } + return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** {@inheritdoc} */ + public function file_exists($path) { + try { + $path = $this->cleanPath($path); + $cachedState = $this->statCache->get($path); + if ($cachedState === false) { + // we know the file doesn't exist + return false; + } elseif (!is_null($cachedState)) { + return true; + } + // need to get from server + return ($this->propfind($path) !== false); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** {@inheritdoc} */ + public function unlink($path) { + $this->init(); + $path = $this->cleanPath($path); + $result = $this->simpleResponse('DELETE', $path, null, 204); + $this->statCache->clear($path . '/'); + $this->statCache->remove($path); + return $result; + } + + /** {@inheritdoc} */ + public function fopen($path, $mode) { + $this->init(); + $path = $this->cleanPath($path); + switch ($mode) { + case 'r': + case 'rb': + try { + $response = $this->httpClientService + ->newClient() + ->get($this->createBaseUri() . $this->encodePath($path), [ + 'auth' => [$this->user, $this->password], + 'stream' => true + ]); + } catch (\GuzzleHttp\Exception\ClientException $e) { + if ($e->getResponse() instanceof ResponseInterface + && $e->getResponse()->getStatusCode() === 404) { + return false; + } else { + throw $e; + } + } + + if ($response->getStatusCode() !== Http::STATUS_OK) { + if ($response->getStatusCode() === Http::STATUS_LOCKED) { + throw new \OCP\Lock\LockedException($path); + } else { + Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR); + } + } + + return $response->getBody(); + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + $tempManager = \OC::$server->getTempManager(); + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + if ($this->file_exists($path)) { + if (!$this->isUpdatable($path)) { + return false; + } + if ($mode === 'w' or $mode === 'w+') { + $tmpFile = $tempManager->getTemporaryFile($ext); + } else { + $tmpFile = $this->getCachedFile($path); + } + } else { + if (!$this->isCreatable(dirname($path))) { + return false; + } + $tmpFile = $tempManager->getTemporaryFile($ext); + } + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { + $this->writeBack($tmpFile, $path); + }); + } + } + + /** + * @param string $tmpFile + */ + public function writeBack($tmpFile, $path) { + $this->uploadFile($tmpFile, $path); + unlink($tmpFile); + } + + /** {@inheritdoc} */ + public function free_space($path) { + $this->init(); + $path = $this->cleanPath($path); + try { + // TODO: cacheable ? + $response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']); + if ($response === false) { + return FileInfo::SPACE_UNKNOWN; + } + if (isset($response['{DAV:}quota-available-bytes'])) { + return (int)$response['{DAV:}quota-available-bytes']; + } else { + return FileInfo::SPACE_UNKNOWN; + } + } catch (\Exception $e) { + return FileInfo::SPACE_UNKNOWN; + } + } + + /** {@inheritdoc} */ + public function touch($path, $mtime = null) { + $this->init(); + if (is_null($mtime)) { + $mtime = time(); + } + $path = $this->cleanPath($path); + + // if file exists, update the mtime, else create a new empty file + if ($this->file_exists($path)) { + try { + $this->statCache->remove($path); + $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]); + // non-owncloud clients might not have accepted the property, need to recheck it + $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0); + if ($response === false) { + return false; + } + if (isset($response['{DAV:}getlastmodified'])) { + $remoteMtime = strtotime($response['{DAV:}getlastmodified']); + if ($remoteMtime !== $mtime) { + // server has not accepted the mtime + return false; + } + } + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 501) { + return false; + } + $this->convertException($e, $path); + return false; + } catch (\Exception $e) { + $this->convertException($e, $path); + return false; + } + } else { + $this->file_put_contents($path, ''); + } + return true; + } + + /** + * @param string $path + * @param string $data + * @return int + */ + public function file_put_contents($path, $data) { + $path = $this->cleanPath($path); + $result = parent::file_put_contents($path, $data); + $this->statCache->remove($path); + return $result; + } + + /** + * @param string $path + * @param string $target + */ + protected function uploadFile($path, $target) { + $this->init(); + + // invalidate + $target = $this->cleanPath($target); + $this->statCache->remove($target); + $source = fopen($path, 'r'); + + $this->httpClientService + ->newClient() + ->put($this->createBaseUri() . $this->encodePath($target), [ + 'body' => $source, + 'auth' => [$this->user, $this->password] + ]); + + $this->removeCachedFile($target); + } + + /** {@inheritdoc} */ + public function rename($path1, $path2) { + $this->init(); + $path1 = $this->cleanPath($path1); + $path2 = $this->cleanPath($path2); + try { + // overwrite directory ? + if ($this->is_dir($path2)) { + // needs trailing slash in destination + $path2 = rtrim($path2, '/') . '/'; + } + $this->client->request( + 'MOVE', + $this->encodePath($path1), + null, + [ + 'Destination' => $this->createBaseUri() . $this->encodePath($path2), + ] + ); + $this->statCache->clear($path1 . '/'); + $this->statCache->clear($path2 . '/'); + $this->statCache->set($path1, false); + $this->statCache->set($path2, true); + $this->removeCachedFile($path1); + $this->removeCachedFile($path2); + return true; + } catch (\Exception $e) { + $this->convertException($e); + } + return false; + } + + /** {@inheritdoc} */ + public function copy($path1, $path2) { + $this->init(); + $path1 = $this->cleanPath($path1); + $path2 = $this->cleanPath($path2); + try { + // overwrite directory ? + if ($this->is_dir($path2)) { + // needs trailing slash in destination + $path2 = rtrim($path2, '/') . '/'; + } + $this->client->request( + 'COPY', + $this->encodePath($path1), + null, + [ + 'Destination' => $this->createBaseUri() . $this->encodePath($path2), + ] + ); + $this->statCache->clear($path2 . '/'); + $this->statCache->set($path2, true); + $this->removeCachedFile($path2); + return true; + } catch (\Exception $e) { + $this->convertException($e); + } + return false; + } + + /** {@inheritdoc} */ + public function stat($path) { + try { + $response = $this->propfind($path); + if (!$response) { + return false; + } + return [ + 'mtime' => strtotime($response['{DAV:}getlastmodified']), + 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, + ]; + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return []; + } + + /** {@inheritdoc} */ + public function getMimeType($path) { + $remoteMimetype = $this->getMimeTypeFromRemote($path); + if ($remoteMimetype === 'application/octet-stream') { + return \OC::$server->getMimeTypeDetector()->detectPath($path); + } else { + return $remoteMimetype; + } + } + + public function getMimeTypeFromRemote($path) { + try { + $response = $this->propfind($path); + if ($response === false) { + return false; + } + $responseType = []; + if (isset($response["{DAV:}resourcetype"])) { + /** @var ResourceType[] $response */ + $responseType = $response["{DAV:}resourcetype"]->getValue(); + } + $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; + if ($type == 'dir') { + return 'httpd/unix-directory'; + } elseif (isset($response['{DAV:}getcontenttype'])) { + return $response['{DAV:}getcontenttype']; + } else { + return 'application/octet-stream'; + } + } catch (\Exception $e) { + return false; + } + } + + /** + * @param string $path + * @return string + */ + public function cleanPath($path) { + if ($path === '') { + return $path; + } + $path = Filesystem::normalizePath($path); + // remove leading slash + return substr($path, 1); + } + + /** + * URL encodes the given path but keeps the slashes + * + * @param string $path to encode + * @return string encoded path + */ + protected function encodePath($path) { + // slashes need to stay + return str_replace('%2F', '/', rawurlencode($path)); + } + + /** + * @param string $method + * @param string $path + * @param string|resource|null $body + * @param int $expected + * @return bool + * @throws StorageInvalidException + * @throws StorageNotAvailableException + */ + protected function simpleResponse($method, $path, $body, $expected) { + $path = $this->cleanPath($path); + try { + $response = $this->client->request($method, $this->encodePath($path), $body); + return $response['statusCode'] == $expected; + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404 && $method === 'DELETE') { + $this->statCache->clear($path . '/'); + $this->statCache->set($path, false); + return false; + } + + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + + /** {@inheritdoc} */ + public function isUpdatable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE); + } + + /** {@inheritdoc} */ + public function isCreatable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE); + } + + /** {@inheritdoc} */ + public function isSharable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE); + } + + /** {@inheritdoc} */ + public function isDeletable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE); + } + + /** {@inheritdoc} */ + public function getPermissions($path) { + $this->init(); + $path = $this->cleanPath($path); + $response = $this->propfind($path); + if ($response === false) { + return 0; + } + if (isset($response['{http://owncloud.org/ns}permissions'])) { + return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + } elseif ($this->is_dir($path)) { + return Constants::PERMISSION_ALL; + } elseif ($this->file_exists($path)) { + return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; + } else { + return 0; + } + } + + /** {@inheritdoc} */ + public function getETag($path) { + $this->init(); + $path = $this->cleanPath($path); + $response = $this->propfind($path); + if ($response === false) { + return null; + } + if (isset($response['{DAV:}getetag'])) { + $etag = trim($response['{DAV:}getetag'], '"'); + if (strlen($etag) > 40) { + $etag = md5($etag); + } + return $etag; + } + return parent::getEtag($path); + } + + /** + * @param string $permissionsString + * @return int + */ + protected function parsePermissions($permissionsString) { + $permissions = Constants::PERMISSION_READ; + if (strpos($permissionsString, 'R') !== false) { + $permissions |= Constants::PERMISSION_SHARE; + } + if (strpos($permissionsString, 'D') !== false) { + $permissions |= Constants::PERMISSION_DELETE; + } + if (strpos($permissionsString, 'W') !== false) { + $permissions |= Constants::PERMISSION_UPDATE; + } + if (strpos($permissionsString, 'CK') !== false) { + $permissions |= Constants::PERMISSION_CREATE; + $permissions |= Constants::PERMISSION_UPDATE; + } + return $permissions; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @throws \OCP\Files\StorageNotAvailableException + * @return bool + */ + public function hasUpdated($path, $time) { + $this->init(); + $path = $this->cleanPath($path); + try { + // force refresh for $path + $this->statCache->remove($path); + $response = $this->propfind($path); + if ($response === false) { + if ($path === '') { + // if root is gone it means the storage is not available + throw new StorageNotAvailableException('root is gone'); + } + return false; + } + if (isset($response['{DAV:}getetag'])) { + $cachedData = $this->getCache()->get($path); + $etag = null; + if (isset($response['{DAV:}getetag'])) { + $etag = trim($response['{DAV:}getetag'], '"'); + } + if (!empty($etag) && $cachedData['etag'] !== $etag) { + return true; + } elseif (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) { + $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions']; + return $sharePermissions !== $cachedData['permissions']; + } elseif (isset($response['{http://owncloud.org/ns}permissions'])) { + $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + return $permissions !== $cachedData['permissions']; + } else { + return false; + } + } else { + $remoteMtime = strtotime($response['{DAV:}getlastmodified']); + return $remoteMtime > $time; + } + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 405) { + if ($path === '') { + // if root is gone it means the storage is not available + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } + return false; + } + $this->convertException($e, $path); + return false; + } catch (\Exception $e) { + $this->convertException($e, $path); + return false; + } + } + + /** + * Interpret the given exception and decide whether it is due to an + * unavailable storage, invalid storage or other. + * This will either throw StorageInvalidException, StorageNotAvailableException + * or do nothing. + * + * @param Exception $e sabre exception + * @param string $path optional path from the operation + * + * @throws StorageInvalidException if the storage is invalid, for example + * when the authentication expired or is invalid + * @throws StorageNotAvailableException if the storage is not available, + * which might be temporary + * @throws ForbiddenException if the action is not allowed + */ + protected function convertException(Exception $e, $path = '') { + \OC::$server->getLogger()->logException($e, ['app' => 'files_external', 'level' => ILogger::DEBUG]); + if ($e instanceof ClientHttpException) { + if ($e->getHttpStatus() === Http::STATUS_LOCKED) { + throw new \OCP\Lock\LockedException($path); + } + if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) { + // either password was changed or was invalid all along + throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage()); + } elseif ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) { + // ignore exception for MethodNotAllowed, false will be returned + return; + } elseif ($e->getHttpStatus() === Http::STATUS_FORBIDDEN) { + // The operation is forbidden. Fail somewhat gracefully + throw new ForbiddenException(get_class($e) . ':' . $e->getMessage(), false); + } + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } elseif ($e instanceof ClientException) { + // connection timeout or refused, server could be temporarily down + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } elseif ($e instanceof \InvalidArgumentException) { + // parse error because the server returned HTML instead of XML, + // possibly temporarily down + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } elseif (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) { + // rethrow + throw $e; + } + + // TODO: only log for now, but in the future need to wrap/rethrow exception + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/FailedStorage.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/FailedStorage.php new file mode 100644 index 0000000..50e2f25 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/FailedStorage.php @@ -0,0 +1,220 @@ + + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\FailedCache; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageNotAvailableException; +use OCP\Lock\ILockingProvider; + +/** + * Storage placeholder to represent a missing precondition, storage unavailable + */ +class FailedStorage extends Common { + + /** @var \Exception */ + protected $e; + + /** + * @param array $params ['exception' => \Exception] + */ + public function __construct($params) { + $this->e = $params['exception']; + if (!$this->e) { + throw new \InvalidArgumentException('Missing "exception" argument in FailedStorage constructor'); + } + } + + public function getId() { + // we can't return anything sane here + return 'failedstorage'; + } + + public function mkdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rmdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function opendir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_dir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_file($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function stat($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filetype($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filesize($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isCreatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isReadable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isUpdatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isDeletable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isSharable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getPermissions($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_exists($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filemtime($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_get_contents($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_put_contents($path, $data) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function unlink($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rename($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function copy($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function fopen($path, $mode) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getMimeType($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hash($type, $path, $raw = false) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function free_space($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function search($query) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function touch($path, $mtime = null) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFile($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFolder($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hasUpdated($path, $time) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getETag($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getDirectDownload($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function verifyPath($path, $fileName) { + return true; + } + + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function acquireLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function releaseLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function changeLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getAvailability() { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function setAvailability($isAvailable) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getCache($path = '', $storage = null) { + return new FailedCache(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Flysystem.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Flysystem.php new file mode 100644 index 0000000..a774782 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Flysystem.php @@ -0,0 +1,260 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\IteratorDirectory; +use League\Flysystem\AdapterInterface; +use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; +use League\Flysystem\Plugin\GetWithMetadata; + +/** + * Generic adapter between flysystem adapters and owncloud's storage system + * + * To use: subclass and call $this->buildFlysystem with the flysystem adapter of choice + */ +abstract class Flysystem extends Common { + /** + * @var Filesystem + */ + protected $flysystem; + + /** + * @var string + */ + protected $root = ''; + + /** + * Initialize the storage backend with a flyssytem adapter + * + * @param \League\Flysystem\AdapterInterface $adapter + */ + protected function buildFlySystem(AdapterInterface $adapter) { + $this->flysystem = new Filesystem($adapter); + $this->flysystem->addPlugin(new GetWithMetadata()); + } + + protected function buildPath($path) { + $fullPath = \OC\Files\Filesystem::normalizePath($this->root . '/' . $path); + return ltrim($fullPath, '/'); + } + + /** + * {@inheritdoc} + */ + public function file_get_contents($path) { + return $this->flysystem->read($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function file_put_contents($path, $data) { + return $this->flysystem->put($this->buildPath($path), $data); + } + + /** + * {@inheritdoc} + */ + public function file_exists($path) { + return $this->flysystem->has($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function unlink($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } + try { + return $this->flysystem->delete($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function rename($source, $target) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + return $this->flysystem->rename($this->buildPath($source), $this->buildPath($target)); + } + + /** + * {@inheritdoc} + */ + public function copy($source, $target) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + return $this->flysystem->copy($this->buildPath($source), $this->buildPath($target)); + } + + /** + * {@inheritdoc} + */ + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } else { + return $this->flysystem->getSize($this->buildPath($path)); + } + } + + /** + * {@inheritdoc} + */ + public function mkdir($path) { + if ($this->file_exists($path)) { + return false; + } + return $this->flysystem->createDir($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function filemtime($path) { + return $this->flysystem->getTimestamp($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function rmdir($path) { + try { + return @$this->flysystem->deleteDir($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function opendir($path) { + try { + $content = $this->flysystem->listContents($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + $names = array_map(function ($object) { + return $object['basename']; + }, $content); + return IteratorDirectory::wrap($names); + } + + /** + * {@inheritdoc} + */ + public function fopen($path, $mode) { + $fullPath = $this->buildPath($path); + $useExisting = true; + switch ($mode) { + case 'r': + case 'rb': + try { + return $this->flysystem->readStream($fullPath); + } catch (FileNotFoundException $e) { + return false; + } + case 'w': + case 'w+': + case 'wb': + case 'wb+': + $useExisting = false; + // no break + case 'a': + case 'ab': + case 'r+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if ($useExisting and $this->file_exists($path)) { + if (!$this->isUpdatable($path)) { + return false; + } + $tmpFile = $this->getCachedFile($path); + } else { + if (!$this->isCreatable(dirname($path))) { + return false; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + } + $source = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath) { + $this->flysystem->putStream($fullPath, fopen($tmpFile, 'r')); + unlink($tmpFile); + }); + } + return false; + } + + /** + * {@inheritdoc} + */ + public function touch($path, $mtime = null) { + if ($this->file_exists($path)) { + return false; + } else { + $this->file_put_contents($path, ''); + return true; + } + } + + /** + * {@inheritdoc} + */ + public function stat($path) { + $info = $this->flysystem->getWithMetadata($this->buildPath($path), ['timestamp', 'size']); + return [ + 'mtime' => $info['timestamp'], + 'size' => $info['size'] + ]; + } + + /** + * {@inheritdoc} + */ + public function filetype($path) { + if ($path === '' or $path === '/' or $path === '.') { + return 'dir'; + } + try { + $info = $this->flysystem->getMetadata($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + return $info['type']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Home.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Home.php new file mode 100644 index 0000000..c725076 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Home.php @@ -0,0 +1,111 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\HomePropagator; + +/** + * Specialized version of Local storage for home directory usage + */ +class Home extends Local implements \OCP\Files\IHomeStorage { + /** + * @var string + */ + protected $id; + + /** + * @var \OC\User\User $user + */ + protected $user; + + /** + * Construct a Home storage instance + * + * @param array $arguments array with "user" containing the + * storage owner + */ + public function __construct($arguments) { + $this->user = $arguments['user']; + $datadir = $this->user->getHome(); + $this->id = 'home::' . $this->user->getUID(); + + parent::__construct(['datadir' => $datadir]); + } + + public function getId() { + return $this->id; + } + + /** + * @return \OC\Files\Cache\HomeCache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->cache)) { + $this->cache = new \OC\Files\Cache\HomeCache($storage); + } + return $this->cache; + } + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->propagator)) { + $this->propagator = new HomePropagator($storage, \OC::$server->getDatabaseConnection()); + } + return $this->propagator; + } + + + /** + * Returns the owner of this home storage + * + * @return \OC\User\User owner of this home storage + */ + public function getUser() { + return $this->user; + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return string uid or false + */ + public function getOwner($path) { + return $this->user->getUID(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Local.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Local.php new file mode 100644 index 0000000..591ee96 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Local.php @@ -0,0 +1,567 @@ + + * @author Bart Visscher + * @author Boris Rybalkin + * @author Brice Maron + * @author Christoph Wurst + * @author J0WI + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Klaas Freitag + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sjors van der Pluijm + * @author Stefan Weil + * @author Thomas Müller + * @author Tigran Mkrtchyan + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Filesystem; +use OC\Files\Storage\Wrapper\Jail; +use OCP\Constants; +use OCP\Files\ForbiddenException; +use OCP\Files\GenericFileException; +use OCP\Files\Storage\IStorage; +use OCP\ILogger; + +/** + * for local filestore, we only have to map the paths + */ +class Local extends \OC\Files\Storage\Common { + protected $datadir; + + protected $dataDirLength; + + protected $allowSymlinks = false; + + protected $realDataDir; + + public function __construct($arguments) { + if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { + throw new \InvalidArgumentException('No data directory set for local storage'); + } + $this->datadir = str_replace('//', '/', $arguments['datadir']); + // some crazy code uses a local storage on root... + if ($this->datadir === '/') { + $this->realDataDir = $this->datadir; + } else { + $realPath = realpath($this->datadir) ?: $this->datadir; + $this->realDataDir = rtrim($realPath, '/') . '/'; + } + if (substr($this->datadir, -1) !== '/') { + $this->datadir .= '/'; + } + $this->dataDirLength = strlen($this->realDataDir); + } + + public function __destruct() { + } + + public function getId() { + return 'local::' . $this->datadir; + } + + public function mkdir($path) { + return @mkdir($this->getSourcePath($path), 0777, true); + } + + public function rmdir($path) { + if (!$this->isDeletable($path)) { + return false; + } + try { + $it = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->getSourcePath($path)), + \RecursiveIteratorIterator::CHILD_FIRST + ); + /** + * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach + * This bug is fixed in PHP 5.5.9 or before + * See #8376 + */ + $it->rewind(); + while ($it->valid()) { + /** + * @var \SplFileInfo $file + */ + $file = $it->current(); + clearstatcache(true, $this->getSourcePath($file)); + if (in_array($file->getBasename(), ['.', '..'])) { + $it->next(); + continue; + } elseif ($file->isDir()) { + rmdir($file->getPathname()); + } elseif ($file->isFile() || $file->isLink()) { + unlink($file->getPathname()); + } + $it->next(); + } + clearstatcache(true, $this->getSourcePath($path)); + return rmdir($this->getSourcePath($path)); + } catch (\UnexpectedValueException $e) { + return false; + } + } + + public function opendir($path) { + return opendir($this->getSourcePath($path)); + } + + public function is_dir($path) { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + return is_dir($this->getSourcePath($path)); + } + + public function is_file($path) { + return is_file($this->getSourcePath($path)); + } + + public function stat($path) { + $fullPath = $this->getSourcePath($path); + clearstatcache(true, $fullPath); + $statResult = stat($fullPath); + if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + $filesize = $this->filesize($path); + $statResult['size'] = $filesize; + $statResult[7] = $filesize; + } + return $statResult; + } + + /** + * @inheritdoc + */ + public function getMetaData($path) { + $fullPath = $this->getSourcePath($path); + clearstatcache(true, $fullPath); + $stat = @stat($fullPath); + if (!$stat) { + return null; + } + + $permissions = Constants::PERMISSION_SHARE; + $statPermissions = $stat['mode']; + $isDir = ($statPermissions & 0x4000) === 0x4000; + if ($statPermissions & 0x0100) { + $permissions += Constants::PERMISSION_READ; + } + if ($statPermissions & 0x0080) { + $permissions += Constants::PERMISSION_UPDATE; + if ($isDir) { + $permissions += Constants::PERMISSION_CREATE; + } + } + + if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions + $parent = dirname($fullPath); + if (is_writable($parent)) { + $permissions += Constants::PERMISSION_DELETE; + } + } + + $data = []; + $data['mimetype'] = $isDir ? 'httpd/unix-directory' : \OC::$server->getMimeTypeDetector()->detectPath($path); + $data['mtime'] = $stat['mtime']; + if ($data['mtime'] === false) { + $data['mtime'] = time(); + } + if ($isDir) { + $data['size'] = -1; //unknown + } else { + $data['size'] = $stat['size']; + } + $data['etag'] = $this->calculateEtag($path, $stat); + $data['storage_mtime'] = $data['mtime']; + $data['permissions'] = $permissions; + $data['name'] = basename($path); + + return $data; + } + + public function filetype($path) { + $filetype = filetype($this->getSourcePath($path)); + if ($filetype == 'link') { + $filetype = filetype(realpath($this->getSourcePath($path))); + } + return $filetype; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } + $fullPath = $this->getSourcePath($path); + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper; + return $helper->getFileSize($fullPath); + } + return filesize($fullPath); + } + + public function isReadable($path) { + return is_readable($this->getSourcePath($path)); + } + + public function isUpdatable($path) { + return is_writable($this->getSourcePath($path)); + } + + public function file_exists($path) { + return file_exists($this->getSourcePath($path)); + } + + public function filemtime($path) { + $fullPath = $this->getSourcePath($path); + clearstatcache(true, $fullPath); + if (!$this->file_exists($path)) { + return false; + } + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper(); + return $helper->getFileMtime($fullPath); + } + return filemtime($fullPath); + } + + public function touch($path, $mtime = null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if ($this->file_exists($path) and !$this->isUpdatable($path)) { + return false; + } + if (!is_null($mtime)) { + $result = @touch($this->getSourcePath($path), $mtime); + } else { + $result = @touch($this->getSourcePath($path)); + } + if ($result) { + clearstatcache(true, $this->getSourcePath($path)); + } + + return $result; + } + + public function file_get_contents($path) { + return file_get_contents($this->getSourcePath($path)); + } + + public function file_put_contents($path, $data) { + return file_put_contents($this->getSourcePath($path), $data); + } + + public function unlink($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } elseif ($this->is_file($path)) { + return unlink($this->getSourcePath($path)); + } else { + return false; + } + } + + private function treeContainsBlacklistedFile(string $path): bool { + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + foreach ($iterator as $file) { + /** @var \SplFileInfo $file */ + if (Filesystem::isFileBlacklisted($file->getBasename())) { + return true; + } + } + + return false; + } + + public function rename($path1, $path2) { + $srcParent = dirname($path1); + $dstParent = dirname($path2); + + if (!$this->isUpdatable($srcParent)) { + \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR); + return false; + } + + if (!$this->isUpdatable($dstParent)) { + \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR); + return false; + } + + if (!$this->file_exists($path1)) { + \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR); + return false; + } + + if ($this->is_dir($path2)) { + $this->rmdir($path2); + } elseif ($this->is_file($path2)) { + $this->unlink($path2); + } + + if ($this->is_dir($path1)) { + // we can't move folders across devices, use copy instead + $stat1 = stat(dirname($this->getSourcePath($path1))); + $stat2 = stat(dirname($this->getSourcePath($path2))); + if ($stat1['dev'] !== $stat2['dev']) { + $result = $this->copy($path1, $path2); + if ($result) { + $result &= $this->rmdir($path1); + } + return $result; + } + + if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) { + throw new ForbiddenException('Invalid path', false); + } + } + + return rename($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + + public function copy($path1, $path2) { + if ($this->is_dir($path1)) { + return parent::copy($path1, $path2); + } else { + return copy($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + } + + public function fopen($path, $mode) { + return fopen($this->getSourcePath($path), $mode); + } + + public function hash($type, $path, $raw = false) { + return hash_file($type, $this->getSourcePath($path), $raw); + } + + public function free_space($path) { + $sourcePath = $this->getSourcePath($path); + // using !is_dir because $sourcePath might be a part file or + // non-existing file, so we'd still want to use the parent dir + // in such cases + if (!is_dir($sourcePath)) { + // disk_free_space doesn't work on files + $sourcePath = dirname($sourcePath); + } + $space = @disk_free_space($sourcePath); + if ($space === false || is_null($space)) { + return \OCP\Files\FileInfo::SPACE_UNKNOWN; + } + return $space; + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->getSourcePath($path); + } + + public function getLocalFolder($path) { + return $this->getSourcePath($path); + } + + /** + * @param string $query + * @param string $dir + * @return array + */ + protected function searchInDir($query, $dir = '') { + $files = []; + $physicalDir = $this->getSourcePath($dir); + foreach (scandir($physicalDir) as $item) { + if (\OC\Files\Filesystem::isIgnoredDir($item)) { + continue; + } + $physicalItem = $physicalDir . '/' . $item; + + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if (is_dir($physicalItem)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + if ($this->file_exists($path)) { + return $this->filemtime($path) > $time; + } else { + return true; + } + } + + /** + * Get the source path (on disk) of a given path + * + * @param string $path + * @return string + * @throws ForbiddenException + */ + public function getSourcePath($path) { + if (Filesystem::isFileBlacklisted($path)) { + throw new ForbiddenException('Invalid path', false); + } + + $fullPath = $this->datadir . $path; + $currentPath = $path; + if ($this->allowSymlinks || $currentPath === '') { + return $fullPath; + } + $pathToResolve = $fullPath; + $realPath = realpath($pathToResolve); + while ($realPath === false) { // for non existing files check the parent directory + $currentPath = dirname($currentPath); + if ($currentPath === '' || $currentPath === '.') { + return $fullPath; + } + $realPath = realpath($this->datadir . $currentPath); + } + if ($realPath) { + $realPath = $realPath . '/'; + } + if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) { + return $fullPath; + } + + \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR); + throw new ForbiddenException('Following symlinks is not allowed', false); + } + + /** + * {@inheritdoc} + */ + public function isLocal() { + return true; + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->calculateEtag($path, $this->stat($path)); + } + + private function calculateEtag(string $path, array $stat): string { + if ($stat['mode'] & 0x4000) { // is_dir + return parent::getETag($path); + } else { + if ($stat === false) { + return md5(''); + } + + $toHash = ''; + if (isset($stat['mtime'])) { + $toHash .= $stat['mtime']; + } + if (isset($stat['ino'])) { + $toHash .= $stat['ino']; + } + if (isset($stat['dev'])) { + $toHash .= $stat['dev']; + } + if (isset($stat['size'])) { + $toHash .= $stat['size']; + } + + return md5($toHash); + } + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + if ($sourceStorage->instanceOfStorage(Local::class)) { + if ($sourceStorage->instanceOfStorage(Jail::class)) { + /** + * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage + */ + $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); + } + /** + * @var \OC\Files\Storage\Local $sourceStorage + */ + $rootStorage = new Local(['datadir' => '/']); + return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); + } else { + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage->instanceOfStorage(Local::class)) { + if ($sourceStorage->instanceOfStorage(Jail::class)) { + /** + * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage + */ + $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); + } + /** + * @var \OC\Files\Storage\Local $sourceStorage + */ + $rootStorage = new Local(['datadir' => '/']); + return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); + } else { + return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } + + public function writeStream(string $path, $stream, int $size = null): int { + $result = file_put_contents($this->getSourcePath($path), $stream); + if ($result === false) { + throw new GenericFileException("Failed write steam to $path"); + } else { + return $result; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalRootStorage.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalRootStorage.php new file mode 100644 index 0000000..6f95421 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalRootStorage.php @@ -0,0 +1,41 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\LocalRootScanner; + +class LocalRootStorage extends Local { + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->scanner)) { + $storage->scanner = new LocalRootScanner($storage); + } + return $storage->scanner; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalTempFileTrait.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalTempFileTrait.php new file mode 100644 index 0000000..0a3785d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/LocalTempFileTrait.php @@ -0,0 +1,81 @@ + + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * Some \OC\Files\Storage\Common methods call functions which are first defined + * in classes which extend it, e.g. $this->stat() . + */ +trait LocalTempFileTrait { + + /** @var string[] */ + protected $cachedFiles = []; + + /** + * @param string $path + * @return string + */ + protected function getCachedFile($path) { + if (!isset($this->cachedFiles[$path])) { + $this->cachedFiles[$path] = $this->toTmpFile($path); + } + return $this->cachedFiles[$path]; + } + + /** + * @param string $path + */ + protected function removeCachedFile($path) { + unset($this->cachedFiles[$path]); + } + + /** + * @param string $path + * @return string + */ + protected function toTmpFile($path) { //no longer in the storage api, still useful here + $source = $this->fopen($path, 'r'); + if (!$source) { + return false; + } + if ($pos = strrpos($path, '.')) { + $extension = substr($path, $pos); + } else { + $extension = ''; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); + $target = fopen($tmpFile, 'w'); + \OC_Helper::streamCopy($source, $target); + fclose($target); + return $tmpFile; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/PolyFill/CopyDirectory.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/PolyFill/CopyDirectory.php new file mode 100644 index 0000000..6a12089 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/PolyFill/CopyDirectory.php @@ -0,0 +1,105 @@ + + * @author Robin Appelman + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\PolyFill; + +trait CopyDirectory { + /** + * Check if a path is a directory + * + * @param string $path + * @return bool + */ + abstract public function is_dir($path); + + /** + * Check if a file or folder exists + * + * @param string $path + * @return bool + */ + abstract public function file_exists($path); + + /** + * Delete a file or folder + * + * @param string $path + * @return bool + */ + abstract public function unlink($path); + + /** + * Open a directory handle for a folder + * + * @param string $path + * @return resource | bool + */ + abstract public function opendir($path); + + /** + * Create a new folder + * + * @param string $path + * @return bool + */ + abstract public function mkdir($path); + + public function copy($source, $target) { + if ($this->is_dir($source)) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + $this->mkdir($target); + return $this->copyRecursive($source, $target); + } else { + return parent::copy($source, $target); + } + } + + /** + * For adapters that don't support copying folders natively + * + * @param $source + * @param $target + * @return bool + */ + protected function copyRecursive($source, $target) { + $dh = $this->opendir($source); + $result = true; + while ($file = readdir($dh)) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->is_dir($source . '/' . $file)) { + $this->mkdir($target . '/' . $file); + $result = $this->copyRecursive($source . '/' . $file, $target . '/' . $file); + } else { + $result = parent::copy($source . '/' . $file, $target . '/' . $file); + } + if (!$result) { + break; + } + } + } + return $result; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Storage.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Storage.php new file mode 100644 index 0000000..56d997a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Storage.php @@ -0,0 +1,141 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OCP\Lock\ILockingProvider; + +/** + * Provide a common interface to all different storage options + * + * All paths passed to the storage are relative to the storage and should NOT have a leading slash. + */ +interface Storage extends \OCP\Files\Storage { + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null); + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null); + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path); + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null); + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null); + + /** + * get a updater instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Updater + */ + public function getUpdater($storage = null); + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache(); + + /** + * @param string $path + * @return array + */ + public function getMetaData($path); + + /** + * @param string $path The path of the file to acquire the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to release the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function releaseLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to change the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function changeLock($path, $type, ILockingProvider $provider); + + /** + * Get the contents of a directory with metadata + * + * @param string $directory + * @return \Traversable an iterator, containing file metadata + * + * The metadata array will contain the following fields + * + * - name + * - mimetype + * - mtime + * - size + * - etag + * - storage_mtime + * - permissions + */ + public function getDirectoryContent($directory): \Traversable; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/StorageFactory.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/StorageFactory.php new file mode 100644 index 0000000..52ddbfb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/StorageFactory.php @@ -0,0 +1,108 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; + +class StorageFactory implements IStorageFactory { + /** + * @var array[] [$name=>['priority'=>$priority, 'wrapper'=>$callable] $storageWrappers + */ + private $storageWrappers = []; + + /** + * allow modifier storage behaviour by adding wrappers around storages + * + * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage + * + * @param string $wrapperName name of the wrapper + * @param callable $callback callback + * @param int $priority wrappers with the lower priority are applied last (meaning they get called first) + * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to + * @return bool true if the wrapper was added, false if there was already a wrapper with this + * name registered + */ + public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) { + if (isset($this->storageWrappers[$wrapperName])) { + return false; + } + + // apply to existing mounts before registering it to prevent applying it double in MountPoint::createStorage + foreach ($existingMounts as $mount) { + $mount->wrapStorage($callback); + } + + $this->storageWrappers[$wrapperName] = ['wrapper' => $callback, 'priority' => $priority]; + return true; + } + + /** + * Remove a storage wrapper by name. + * Note: internal method only to be used for cleanup + * + * @param string $wrapperName name of the wrapper + * @internal + */ + public function removeStorageWrapper($wrapperName) { + unset($this->storageWrappers[$wrapperName]); + } + + /** + * Create an instance of a storage and apply the registered storage wrappers + * + * @param \OCP\Files\Mount\IMountPoint $mountPoint + * @param string $class + * @param array $arguments + * @return \OCP\Files\Storage + */ + public function getInstance(IMountPoint $mountPoint, $class, $arguments) { + return $this->wrap($mountPoint, new $class($arguments)); + } + + /** + * @param \OCP\Files\Mount\IMountPoint $mountPoint + * @param \OCP\Files\Storage $storage + * @return \OCP\Files\Storage + */ + public function wrap(IMountPoint $mountPoint, $storage) { + $wrappers = array_values($this->storageWrappers); + usort($wrappers, function ($a, $b) { + return $b['priority'] - $a['priority']; + }); + /** @var callable[] $wrappers */ + $wrappers = array_map(function ($wrapper) { + return $wrapper['wrapper']; + }, $wrappers); + foreach ($wrappers as $wrapper) { + $storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint); + if (!($storage instanceof \OCP\Files\Storage)) { + throw new \Exception('Invalid result from storage wrapper'); + } + } + return $storage; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Temporary.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Temporary.php new file mode 100644 index 0000000..5e40242 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Temporary.php @@ -0,0 +1,49 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +/** + * local storage backend in temporary folder for testing purpose + */ +class Temporary extends Local { + public function __construct($arguments = null) { + parent::__construct(['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]); + } + + public function cleanUp() { + \OC_Helper::rmdirr($this->datadir); + } + + public function __destruct() { + parent::__destruct(); + $this->cleanUp(); + } + + public function getDataDir() { + return $this->datadir; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Availability.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Availability.php new file mode 100644 index 0000000..61814a0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Availability.php @@ -0,0 +1,476 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageAuthException; +use OCP\Files\StorageNotAvailableException; +use OCP\IConfig; + +/** + * Availability checker for storages + * + * Throws a StorageNotAvailableException for storages with known failures + */ +class Availability extends Wrapper { + public const RECHECK_TTL_SEC = 600; // 10 minutes + + /** @var IConfig */ + protected $config; + + public function __construct($parameters) { + $this->config = $parameters['config'] ?? \OC::$server->getConfig(); + parent::__construct($parameters); + } + + public static function shouldRecheck($availability) { + if (!$availability['available']) { + // trigger a recheck if TTL reached + if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) { + return true; + } + } + return false; + } + + /** + * Only called if availability === false + * + * @return bool + */ + private function updateAvailability() { + // reset availability to false so that multiple requests don't recheck concurrently + $this->setAvailability(false); + try { + $result = $this->test(); + } catch (\Exception $e) { + $result = false; + } + $this->setAvailability($result); + return $result; + } + + /** + * @return bool + */ + private function isAvailable() { + $availability = $this->getAvailability(); + if (self::shouldRecheck($availability)) { + return $this->updateAvailability(); + } + return $availability['available']; + } + + /** + * @throws StorageNotAvailableException + */ + private function checkAvailability() { + if (!$this->isAvailable()) { + throw new StorageNotAvailableException(); + } + } + + /** {@inheritdoc} */ + public function mkdir($path) { + $this->checkAvailability(); + try { + return parent::mkdir($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function rmdir($path) { + $this->checkAvailability(); + try { + return parent::rmdir($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function opendir($path) { + $this->checkAvailability(); + try { + return parent::opendir($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function is_dir($path) { + $this->checkAvailability(); + try { + return parent::is_dir($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function is_file($path) { + $this->checkAvailability(); + try { + return parent::is_file($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function stat($path) { + $this->checkAvailability(); + try { + return parent::stat($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function filetype($path) { + $this->checkAvailability(); + try { + return parent::filetype($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function filesize($path) { + $this->checkAvailability(); + try { + return parent::filesize($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function isCreatable($path) { + $this->checkAvailability(); + try { + return parent::isCreatable($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function isReadable($path) { + $this->checkAvailability(); + try { + return parent::isReadable($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function isUpdatable($path) { + $this->checkAvailability(); + try { + return parent::isUpdatable($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function isDeletable($path) { + $this->checkAvailability(); + try { + return parent::isDeletable($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function isSharable($path) { + $this->checkAvailability(); + try { + return parent::isSharable($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getPermissions($path) { + $this->checkAvailability(); + try { + return parent::getPermissions($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function file_exists($path) { + if ($path === '') { + return true; + } + $this->checkAvailability(); + try { + return parent::file_exists($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function filemtime($path) { + $this->checkAvailability(); + try { + return parent::filemtime($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function file_get_contents($path) { + $this->checkAvailability(); + try { + return parent::file_get_contents($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function file_put_contents($path, $data) { + $this->checkAvailability(); + try { + return parent::file_put_contents($path, $data); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function unlink($path) { + $this->checkAvailability(); + try { + return parent::unlink($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function rename($path1, $path2) { + $this->checkAvailability(); + try { + return parent::rename($path1, $path2); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function copy($path1, $path2) { + $this->checkAvailability(); + try { + return parent::copy($path1, $path2); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function fopen($path, $mode) { + $this->checkAvailability(); + try { + return parent::fopen($path, $mode); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getMimeType($path) { + $this->checkAvailability(); + try { + return parent::getMimeType($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function hash($type, $path, $raw = false) { + $this->checkAvailability(); + try { + return parent::hash($type, $path, $raw); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function free_space($path) { + $this->checkAvailability(); + try { + return parent::free_space($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function search($query) { + $this->checkAvailability(); + try { + return parent::search($query); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function touch($path, $mtime = null) { + $this->checkAvailability(); + try { + return parent::touch($path, $mtime); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getLocalFile($path) { + $this->checkAvailability(); + try { + return parent::getLocalFile($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function hasUpdated($path, $time) { + $this->checkAvailability(); + try { + return parent::hasUpdated($path, $time); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getOwner($path) { + try { + return parent::getOwner($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getETag($path) { + $this->checkAvailability(); + try { + return parent::getETag($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getDirectDownload($path) { + $this->checkAvailability(); + try { + return parent::getDirectDownload($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $this->checkAvailability(); + try { + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $this->checkAvailability(); + try { + return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** {@inheritdoc} */ + public function getMetaData($path) { + $this->checkAvailability(); + try { + return parent::getMetaData($path); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } + + /** + * @throws StorageNotAvailableException + */ + protected function setUnavailable(StorageNotAvailableException $e) { + $delay = self::RECHECK_TTL_SEC; + if ($e instanceof StorageAuthException) { + $delay = max( + // 30min + $this->config->getSystemValueInt('external_storage.auth_availability_delay', 1800), + self::RECHECK_TTL_SEC + ); + } + $this->getStorageCache()->setAvailability(false, $delay); + throw $e; + } + + + + public function getDirectoryContent($directory): \Traversable { + $this->checkAvailability(); + try { + return parent::getDirectoryContent($directory); + } catch (StorageNotAvailableException $e) { + $this->setUnavailable($e); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encoding.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encoding.php new file mode 100644 index 0000000..a2ef178 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encoding.php @@ -0,0 +1,542 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Cache\CappedMemoryCache; +use OCP\Files\Storage\IStorage; +use OCP\ICache; + +/** + * Encoding wrapper that deals with file names that use unsupported encodings like NFD. + * + * When applied and a UTF-8 path name was given, the wrapper will first attempt to access + * the actual given name and then try its NFD form. + */ +class Encoding extends Wrapper { + + /** + * @var ICache + */ + private $namesCache; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + $this->namesCache = new CappedMemoryCache(); + } + + /** + * Returns whether the given string is only made of ASCII characters + * + * @param string $str string + * + * @return bool true if the string is all ASCII, false otherwise + */ + private function isAscii($str) { + return (bool) !preg_match('/[\\x80-\\xff]+/', $str); + } + + /** + * Checks whether the given path exists in NFC or NFD form after checking + * each form for each path section and returns the correct form. + * If no existing path found, returns the path as it was given. + * + * @param string $fullPath path to check + * + * @return string original or converted path + */ + private function findPathToUse($fullPath) { + $cachedPath = $this->namesCache[$fullPath]; + if ($cachedPath !== null) { + return $cachedPath; + } + + $sections = explode('/', $fullPath); + $path = ''; + foreach ($sections as $section) { + $convertedPath = $this->findPathToUseLastSection($path, $section); + if ($convertedPath === null) { + // no point in continuing if the section was not found, use original path + return $fullPath; + } + $path = $convertedPath . '/'; + } + $path = rtrim($path, '/'); + return $path; + } + + /** + * Checks whether the last path section of the given path exists in NFC or NFD form + * and returns the correct form. If no existing path found, returns null. + * + * @param string $basePath base path to check + * @param string $lastSection last section of the path to check for NFD/NFC variations + * + * @return string|null original or converted path, or null if none of the forms was found + */ + private function findPathToUseLastSection($basePath, $lastSection) { + $fullPath = $basePath . $lastSection; + if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) { + $this->namesCache[$fullPath] = $fullPath; + return $fullPath; + } + + // swap encoding + if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) { + $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D); + } else { + $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C); + } + $otherFullPath = $basePath . $otherFormPath; + if ($this->storage->file_exists($otherFullPath)) { + $this->namesCache[$fullPath] = $otherFullPath; + return $otherFullPath; + } + + // return original path, file did not exist at all + $this->namesCache[$fullPath] = $fullPath; + return null; + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + // note: no conversion here, method should not be called with non-NFC names! + $result = $this->storage->mkdir($path); + if ($result) { + $this->namesCache[$path] = $path; + } + return $result; + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + $result = $this->storage->rmdir($this->findPathToUse($path)); + if ($result) { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($this->findPathToUse($path)); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($this->findPathToUse($path)); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($this->findPathToUse($path)); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($this->findPathToUse($path)); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($this->findPathToUse($path)); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($this->findPathToUse($path)); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->storage->file_put_contents($this->findPathToUse($path), $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + $result = $this->storage->unlink($this->findPathToUse($path)); + if ($result) { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + // second name always NFC + return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2)); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2)); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $result = $this->storage->fopen($this->findPathToUse($path), $mode); + if ($result && $mode !== 'r' && $mode !== 'rb') { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $this->findPathToUse($path), $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($this->findPathToUse($path)); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($this->findPathToUse($path), $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($this->findPathToUse($path)); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($this->findPathToUse($path), $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getCache($this->findPathToUse($path), $storage); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getScanner($this->findPathToUse($path), $storage); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($this->findPathToUse($path)); + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath)); + } + + $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$sourceInternalPath]); + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$sourceInternalPath]); + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->storage->getMetaData($this->findPathToUse($path)); + } + + public function getDirectoryContent($directory): \Traversable { + return $this->storage->getDirectoryContent($this->findPathToUse($directory)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encryption.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encryption.php new file mode 100644 index 0000000..3a97764 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Encryption.php @@ -0,0 +1,1042 @@ + + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Piotr M + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; +use OC\Encryption\Update; +use OC\Encryption\Util; +use OC\Files\Cache\CacheEntry; +use OC\Files\Filesystem; +use OC\Files\Mount\Manager; +use OC\Files\Storage\LocalTempFileTrait; +use OC\Memcache\ArrayCache; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Encryption\IFile; +use OCP\Encryption\IManager; +use OCP\Encryption\Keys\IStorage; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage; +use OCP\ILogger; + +class Encryption extends Wrapper { + use LocalTempFileTrait; + + /** @var string */ + private $mountPoint; + + /** @var \OC\Encryption\Util */ + private $util; + + /** @var \OCP\Encryption\IManager */ + private $encryptionManager; + + /** @var \OCP\ILogger */ + private $logger; + + /** @var string */ + private $uid; + + /** @var array */ + protected $unencryptedSize; + + /** @var \OCP\Encryption\IFile */ + private $fileHelper; + + /** @var IMountPoint */ + private $mount; + + /** @var IStorage */ + private $keyStorage; + + /** @var Update */ + private $update; + + /** @var Manager */ + private $mountManager; + + /** @var array remember for which path we execute the repair step to avoid recursions */ + private $fixUnencryptedSizeOf = []; + + /** @var ArrayCache */ + private $arrayCache; + + /** + * @param array $parameters + * @param IManager $encryptionManager + * @param Util $util + * @param ILogger $logger + * @param IFile $fileHelper + * @param string $uid + * @param IStorage $keyStorage + * @param Update $update + * @param Manager $mountManager + * @param ArrayCache $arrayCache + */ + public function __construct( + $parameters, + IManager $encryptionManager = null, + Util $util = null, + ILogger $logger = null, + IFile $fileHelper = null, + $uid = null, + IStorage $keyStorage = null, + Update $update = null, + Manager $mountManager = null, + ArrayCache $arrayCache = null + ) { + $this->mountPoint = $parameters['mountPoint']; + $this->mount = $parameters['mount']; + $this->encryptionManager = $encryptionManager; + $this->util = $util; + $this->logger = $logger; + $this->uid = $uid; + $this->fileHelper = $fileHelper; + $this->keyStorage = $keyStorage; + $this->unencryptedSize = []; + $this->update = $update; + $this->mountManager = $mountManager; + $this->arrayCache = $arrayCache; + parent::__construct($parameters); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + $fullPath = $this->getFullPath($path); + + /** @var CacheEntry $info */ + $info = $this->getCache()->get($path); + if (isset($this->unencryptedSize[$fullPath])) { + $size = $this->unencryptedSize[$fullPath]; + // update file cache + if ($info instanceof ICacheEntry) { + $info = $info->getData(); + $info['encrypted'] = $info['encryptedVersion']; + } else { + if (!is_array($info)) { + $info = []; + } + $info['encrypted'] = true; + } + + $info['size'] = $size; + $this->getCache()->put($path, $info); + + return $size; + } + + if (isset($info['fileid']) && $info['encrypted']) { + return $this->verifyUnencryptedSize($path, $info['size']); + } + + return $this->storage->filesize($path); + } + + private function modifyMetaData(string $path, array $data): array { + $fullPath = $this->getFullPath($path); + $info = $this->getCache()->get($path); + + if (isset($this->unencryptedSize[$fullPath])) { + $data['encrypted'] = true; + $data['size'] = $this->unencryptedSize[$fullPath]; + } else { + if (isset($info['fileid']) && $info['encrypted']) { + $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); + $data['encrypted'] = true; + } + } + + if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) { + $data['encryptedVersion'] = $info['encryptedVersion']; + } + + return $data; + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + $data = $this->storage->getMetaData($path); + if (is_null($data)) { + return null; + } + return $this->modifyMetaData($path, $data); + } + + public function getDirectoryContent($directory): \Traversable { + $parent = rtrim($directory, '/'); + foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) { + yield $this->modifyMetaData($parent . '/' . $data['name'], $data); + } + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + $encryptionModule = $this->getEncryptionModule($path); + + if ($encryptionModule) { + $handle = $this->fopen($path, "r"); + if (!$handle) { + return false; + } + $data = stream_get_contents($handle); + fclose($handle); + return $data; + } + return $this->storage->file_get_contents($path); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + // file put content will always be translated to a stream write + $handle = $this->fopen($path, 'w'); + if (is_resource($handle)) { + $written = fwrite($handle, $data); + fclose($handle); + return $written; + } + + return false; + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + $fullPath = $this->getFullPath($path); + if ($this->util->isExcluded($fullPath)) { + return $this->storage->unlink($path); + } + + $encryptionModule = $this->getEncryptionModule($path); + if ($encryptionModule) { + $this->keyStorage->deleteAllFileKeys($fullPath); + } + + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + $result = $this->storage->rename($path1, $path2); + + if ($result && + // versions always use the keys from the original file, so we can skip + // this step for versions + $this->isVersion($path2) === false && + $this->encryptionManager->isEnabled()) { + $source = $this->getFullPath($path1); + if (!$this->util->isExcluded($source)) { + $target = $this->getFullPath($path2); + if (isset($this->unencryptedSize[$source])) { + $this->unencryptedSize[$target] = $this->unencryptedSize[$source]; + } + $this->keyStorage->renameKeys($source, $target); + $module = $this->getEncryptionModule($path2); + if ($module) { + $module->update($target, $this->uid, []); + } + } + } + + return $result; + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + $result = $this->storage->rmdir($path); + $fullPath = $this->getFullPath($path); + if ($result && + $this->util->isExcluded($fullPath) === false && + $this->encryptionManager->isEnabled() + ) { + $this->keyStorage->deleteAllFileKeys($fullPath); + } + + return $result; + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + $isReadable = true; + + $metaData = $this->getMetaData($path); + if ( + !$this->is_dir($path) && + isset($metaData['encrypted']) && + $metaData['encrypted'] === true + ) { + $fullPath = $this->getFullPath($path); + $module = $this->getEncryptionModule($path); + $isReadable = $module->isReadable($fullPath, $this->uid); + } + + return $this->storage->isReadable($path) && $isReadable; + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + $source = $this->getFullPath($path1); + + if ($this->util->isExcluded($source)) { + return $this->storage->copy($path1, $path2); + } + + // need to stream copy file by file in case we copy between a encrypted + // and a unencrypted storage + $this->unlink($path2); + return $this->copyFromStorage($this, $path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource|bool + * @throws GenericEncryptionException + * @throws ModuleDoesNotExistsException + */ + public function fopen($path, $mode) { + + // check if the file is stored in the array cache, this means that we + // copy a file over to the versions folder, in this case we don't want to + // decrypt it + if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) { + $this->arrayCache->remove('encryption_copy_version_' . $path); + return $this->storage->fopen($path, $mode); + } + + $encryptionEnabled = $this->encryptionManager->isEnabled(); + $shouldEncrypt = false; + $encryptionModule = null; + $header = $this->getHeader($path); + $signed = isset($header['signed']) && $header['signed'] === 'true'; + $fullPath = $this->getFullPath($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + + if ($this->util->isExcluded($fullPath) === false) { + $size = $unencryptedSize = 0; + $realFile = $this->util->stripPartialFileExtension($path); + $targetExists = $this->file_exists($realFile) || $this->file_exists($path); + $targetIsEncrypted = false; + if ($targetExists) { + // in case the file exists we require the explicit module as + // specified in the file header - otherwise we need to fail hard to + // prevent data loss on client side + if (!empty($encryptionModuleId)) { + $targetIsEncrypted = true; + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } + + if ($this->file_exists($path)) { + $size = $this->storage->filesize($path); + $unencryptedSize = $this->filesize($path); + } else { + $size = $unencryptedSize = 0; + } + } + + try { + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + // if we update a encrypted file with a un-encrypted one we change the db flag + if ($targetIsEncrypted && $encryptionEnabled === false) { + $cache = $this->storage->getCache(); + if ($cache) { + $entry = $cache->get($path); + $cache->update($entry->getId(), ['encrypted' => 0]); + } + } + if ($encryptionEnabled) { + // if $encryptionModuleId is empty, the default module will be used + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath); + $signed = true; + } + } else { + $info = $this->getCache()->get($path); + // only get encryption module if we found one in the header + // or if file should be encrypted according to the file cache + if (!empty($encryptionModuleId)) { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + $shouldEncrypt = true; + } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) { + // we come from a old installation. No header and/or no module defined + // but the file is encrypted. In this case we need to use the + // OC_DEFAULT_MODULE to read the file + $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE'); + $shouldEncrypt = true; + $targetIsEncrypted = true; + } + } + } catch (ModuleDoesNotExistsException $e) { + $this->logger->logException($e, [ + 'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted', + 'level' => ILogger::WARN, + 'app' => 'core', + ]); + } + + // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt + if (!$encryptionEnabled || !$this->shouldEncrypt($path)) { + if (!$targetExists || !$targetIsEncrypted) { + $shouldEncrypt = false; + } + } + + if ($shouldEncrypt === true && $encryptionModule !== null) { + $headerSize = $this->getHeaderSize($path); + $source = $this->storage->fopen($path, $mode); + if (!is_resource($source)) { + return false; + } + $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header, + $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode, + $size, $unencryptedSize, $headerSize, $signed); + return $handle; + } + } + + return $this->storage->fopen($path, $mode); + } + + + /** + * perform some plausibility checks if the the unencrypted size is correct. + * If not, we calculate the correct unencrypted size and return it + * + * @param string $path internal path relative to the storage root + * @param int $unencryptedSize size of the unencrypted file + * + * @return int unencrypted size + */ + protected function verifyUnencryptedSize($path, $unencryptedSize) { + $size = $this->storage->filesize($path); + $result = $unencryptedSize; + + if ($unencryptedSize < 0 || + ($size > 0 && $unencryptedSize === $size) + ) { + // check if we already calculate the unencrypted size for the + // given path to avoid recursions + if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) { + $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true; + try { + $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize); + } catch (\Exception $e) { + $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path); + $this->logger->logException($e); + } + unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]); + } + } + + return $result; + } + + /** + * calculate the unencrypted size + * + * @param string $path internal path relative to the storage root + * @param int $size size of the physical file + * @param int $unencryptedSize size of the unencrypted file + * + * @return int calculated unencrypted size + */ + protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + $headerSize = $this->getHeaderSize($path); + $header = $this->getHeader($path); + $encryptionModule = $this->getEncryptionModule($path); + + $stream = $this->storage->fopen($path, 'r'); + + // if we couldn't open the file we return the old unencrypted size + if (!is_resource($stream)) { + $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.'); + return $unencryptedSize; + } + + $newUnencryptedSize = 0; + $size -= $headerSize; + $blockSize = $this->util->getBlockSize(); + + // if a header exists we skip it + if ($headerSize > 0) { + fread($stream, $headerSize); + } + + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + return 0; + } + + $signed = isset($header['signed']) && $header['signed'] === 'true'; + $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed); + + // calculate last chunk nr + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + + $lastChunkNr = ceil($size / $blockSize) - 1; + // calculate last chunk position + $lastChunkPos = ($lastChunkNr * $blockSize); + // try to fseek to the last chunk, if it fails we have to read the whole file + if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { + $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize; + } + + $lastChunkContentEncrypted = ''; + $count = $blockSize; + + while ($count > 0) { + $data = fread($stream, $blockSize); + $count = strlen($data); + $lastChunkContentEncrypted .= $data; + if (strlen($lastChunkContentEncrypted) > $blockSize) { + $newUnencryptedSize += $unencryptedBlockSize; + $lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize); + } + } + + fclose($stream); + + // we have to decrypt the last chunk to get it actual size + $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []); + $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end'); + $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end'); + + // calc the real file size with the size of the last chunk + $newUnencryptedSize += strlen($decryptedLastChunk); + + $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize); + + // write to cache if applicable + $cache = $this->storage->getCache(); + if ($cache) { + $entry = $cache->get($path); + $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); + } + + return $newUnencryptedSize; + } + + /** + * @param Storage\IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @return bool + */ + public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: + // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage + // - copy the file cache update from $this->copyBetweenStorage to this method + // - copy the copyKeys() call from $this->copyBetweenStorage to this method + // - remove $this->copyBetweenStorage + + if (!$sourceStorage->isDeletable($sourceInternalPath)) { + return false; + } + + $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true); + if ($result) { + if ($sourceStorage->is_dir($sourceInternalPath)) { + $result &= $sourceStorage->rmdir($sourceInternalPath); + } else { + $result &= $sourceStorage->unlink($sourceInternalPath); + } + } + return $result; + } + + + /** + * @param Storage\IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @param bool $isRename + * @return bool + */ + public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { + + // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: + // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage + // - copy the file cache update from $this->copyBetweenStorage to this method + // - copy the copyKeys() call from $this->copyBetweenStorage to this method + // - remove $this->copyBetweenStorage + + return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename); + } + + /** + * Update the encrypted cache version in the database + * + * @param Storage\IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $isRename + * @param bool $keepEncryptionVersion + */ + private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) { + $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath); + $cacheInformation = [ + 'encrypted' => $isEncrypted, + ]; + if ($isEncrypted) { + $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion']; + + // In case of a move operation from an unencrypted to an encrypted + // storage the old encrypted version would stay with "0" while the + // correct value would be "1". Thus we manually set the value to "1" + // for those cases. + // See also https://github.com/owncloud/core/issues/23078 + if ($encryptedVersion === 0 || !$keepEncryptionVersion) { + $encryptedVersion = 1; + } + + $cacheInformation['encryptedVersion'] = $encryptedVersion; + } + + // in case of a rename we need to manipulate the source cache because + // this information will be kept for the new target + if ($isRename) { + $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation); + } else { + $this->getCache()->put($targetInternalPath, $cacheInformation); + } + } + + /** + * copy file between two storages + * + * @param Storage\IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @param bool $isRename + * @return bool + * @throws \Exception + */ + private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { + + // for versions we have nothing to do, because versions should always use the + // key from the original file. Just create a 1:1 copy and done + if ($this->isVersion($targetInternalPath) || + $this->isVersion($sourceInternalPath)) { + // remember that we try to create a version so that we can detect it during + // fopen($sourceInternalPath) and by-pass the encryption in order to + // create a 1:1 copy of the file + $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true); + $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath); + if ($result) { + $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath); + // make sure that we update the unencrypted size for the version + if (isset($info['encrypted']) && $info['encrypted'] === true) { + $this->updateUnencryptedSize( + $this->getFullPath($targetInternalPath), + $info['size'] + ); + } + $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true); + } + return $result; + } + + // first copy the keys that we reuse the existing file key on the target location + // and don't create a new one which would break versions for example. + $mount = $this->mountManager->findByStorageId($sourceStorage->getId()); + if (count($mount) === 1) { + $mountPoint = $mount[0]->getMountPoint(); + $source = $mountPoint . '/' . $sourceInternalPath; + $target = $this->getFullPath($targetInternalPath); + $this->copyKeys($source, $target); + } else { + $this->logger->error('Could not find mount point, can\'t keep encryption keys'); + } + + if ($sourceStorage->is_dir($sourceInternalPath)) { + $dh = $sourceStorage->opendir($sourceInternalPath); + $result = $this->mkdir($targetInternalPath); + if (is_resource($dh)) { + while ($result and ($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename); + } + } + } + } else { + try { + $source = $sourceStorage->fopen($sourceInternalPath, 'r'); + $target = $this->fopen($targetInternalPath, 'w'); + [, $result] = \OC_Helper::streamCopy($source, $target); + fclose($source); + fclose($target); + } catch (\Exception $e) { + fclose($source); + fclose($target); + throw $e; + } + if ($result) { + if ($preserveMtime) { + $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); + } + $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false); + } else { + // delete partially written target file + $this->unlink($targetInternalPath); + // delete cache entry that was created by fopen + $this->getCache()->remove($targetInternalPath); + } + } + return (bool)$result; + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + if ($this->encryptionManager->isEnabled()) { + $cachedFile = $this->getCachedFile($path); + if (is_string($cachedFile)) { + return $cachedFile; + } + } + return $this->storage->getLocalFile($path); + } + + /** + * Returns the wrapped storage's value for isLocal() + * + * @return bool wrapped storage's isLocal() value + */ + public function isLocal() { + if ($this->encryptionManager->isEnabled()) { + return false; + } + return $this->storage->isLocal(); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + $stat = $this->storage->stat($path); + $fileSize = $this->filesize($path); + $stat['size'] = $fileSize; + $stat[7] = $fileSize; + $stat['hasHeader'] = $this->getHeaderSize($path) > 0; + return $stat; + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + $fh = $this->fopen($path, 'rb'); + $ctx = hash_init($type); + hash_update_stream($ctx, $fh); + fclose($fh); + return hash_final($ctx, $raw); + } + + /** + * return full path, including mount point + * + * @param string $path relative to mount point + * @return string full path including mount point + */ + protected function getFullPath($path) { + return Filesystem::normalizePath($this->mountPoint . '/' . $path); + } + + /** + * read first block of encrypted file, typically this will contain the + * encryption header + * + * @param string $path + * @return string + */ + protected function readFirstBlock($path) { + $firstBlock = ''; + if ($this->storage->file_exists($path)) { + $handle = $this->storage->fopen($path, 'r'); + $firstBlock = fread($handle, $this->util->getHeaderSize()); + fclose($handle); + } + return $firstBlock; + } + + /** + * return header size of given file + * + * @param string $path + * @return int + */ + protected function getHeaderSize($path) { + $headerSize = 0; + $realFile = $this->util->stripPartialFileExtension($path); + if ($this->storage->file_exists($realFile)) { + $path = $realFile; + } + $firstBlock = $this->readFirstBlock($path); + + if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { + $headerSize = $this->util->getHeaderSize(); + } + + return $headerSize; + } + + /** + * parse raw header to array + * + * @param string $rawHeader + * @return array + */ + protected function parseRawHeader($rawHeader) { + $result = []; + if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { + $header = $rawHeader; + $endAt = strpos($header, Util::HEADER_END); + if ($endAt !== false) { + $header = substr($header, 0, $endAt + strlen(Util::HEADER_END)); + + // +1 to not start with an ':' which would result in empty element at the beginning + $exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1)); + + $element = array_shift($exploded); + while ($element !== Util::HEADER_END) { + $result[$element] = array_shift($exploded); + $element = array_shift($exploded); + } + } + } + + return $result; + } + + /** + * read header from file + * + * @param string $path + * @return array + */ + protected function getHeader($path) { + $realFile = $this->util->stripPartialFileExtension($path); + $exists = $this->storage->file_exists($realFile); + if ($exists) { + $path = $realFile; + } + + $firstBlock = $this->readFirstBlock($path); + $result = $this->parseRawHeader($firstBlock); + + // if the header doesn't contain a encryption module we check if it is a + // legacy file. If true, we add the default encryption module + if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) { + if (!empty($result)) { + $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; + } elseif ($exists) { + // if the header was empty we have to check first if it is a encrypted file at all + // We would do query to filecache only if we know that entry in filecache exists + $info = $this->getCache()->get($path); + if (isset($info['encrypted']) && $info['encrypted'] === true) { + $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; + } + } + } + + return $result; + } + + /** + * read encryption module needed to read/write the file located at $path + * + * @param string $path + * @return null|\OCP\Encryption\IEncryptionModule + * @throws ModuleDoesNotExistsException + * @throws \Exception + */ + protected function getEncryptionModule($path) { + $encryptionModule = null; + $header = $this->getHeader($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + if (!empty($encryptionModuleId)) { + try { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } catch (ModuleDoesNotExistsException $e) { + $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!'); + throw $e; + } + } + + return $encryptionModule; + } + + /** + * @param string $path + * @param int $unencryptedSize + */ + public function updateUnencryptedSize($path, $unencryptedSize) { + $this->unencryptedSize[$path] = $unencryptedSize; + } + + /** + * copy keys to new location + * + * @param string $source path relative to data/ + * @param string $target path relative to data/ + * @return bool + */ + protected function copyKeys($source, $target) { + if (!$this->util->isExcluded($source)) { + return $this->keyStorage->copyKeys($source, $target); + } + + return false; + } + + /** + * check if path points to a files version + * + * @param $path + * @return bool + */ + protected function isVersion($path) { + $normalized = Filesystem::normalizePath($path); + return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/'; + } + + /** + * check if the given storage should be encrypted or not + * + * @param $path + * @return bool + */ + protected function shouldEncrypt($path) { + $fullPath = $this->getFullPath($path); + $mountPointConfig = $this->mount->getOption('encrypt', true); + if ($mountPointConfig === false) { + return false; + } + + try { + $encryptionModule = $this->getEncryptionModule($fullPath); + } catch (ModuleDoesNotExistsException $e) { + return false; + } + + if ($encryptionModule === null) { + $encryptionModule = $this->encryptionManager->getEncryptionModule(); + } + + return $encryptionModule->shouldEncrypt($fullPath); + } + + public function writeStream(string $path, $stream, int $size = null): int { + // always fall back to fopen + $target = $this->fopen($path, 'w'); + [$count, $result] = \OC_Helper::streamCopy($stream, $target); + fclose($target); + return $count; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Jail.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Jail.php new file mode 100644 index 0000000..449a238 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Jail.php @@ -0,0 +1,542 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Files\Cache\Wrapper\CacheJail; +use OC\Files\Cache\Wrapper\JailPropagator; +use OC\Files\Filesystem; +use OCP\Files\Storage\IStorage; +use OCP\Files\Storage\IWriteStreamStorage; +use OCP\Lock\ILockingProvider; + +/** + * Jail to a subdirectory of the wrapped storage + * + * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage + */ +class Jail extends Wrapper { + /** + * @var string + */ + protected $rootPath; + + /** + * @param array $arguments ['storage' => $storage, 'mask' => $root] + * + * $storage: The storage that will be wrapper + * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage + */ + public function __construct($arguments) { + parent::__construct($arguments); + $this->rootPath = $arguments['root']; + } + + public function getUnjailedPath($path) { + return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/'); + } + + /** + * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper + */ + public function getUnjailedStorage() { + return $this->storage; + } + + + public function getJailedPath($path) { + $root = rtrim($this->rootPath, '/') . '/'; + + if ($path !== $this->rootPath && strpos($path, $root) !== 0) { + return null; + } else { + $path = substr($path, strlen($this->rootPath)); + return trim($path, '/'); + } + } + + public function getId() { + return parent::getId(); + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->getWrapperStorage()->stat($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path)); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path)); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path)); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path)); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path)); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path)); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path)); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path)); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->getWrapperStorage()->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path)); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this->getWrapperStorage(); + } + $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage); + return new CacheJail($sourceCache, $this->rootPath); + } + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path)); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path)); + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path)); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function changeLock($path, $type, ILockingProvider $provider) { + $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider); + } + + /** + * Resolve the path for the source of the share + * + * @param string $path + * @return array + */ + public function resolvePath($path) { + return [$this->getWrapperStorage(), $this->getUnjailedPath($path)]; + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); + } + + public function getPropagator($storage = null) { + if (isset($this->propagator)) { + return $this->propagator; + } + + if (!$storage) { + $storage = $this; + } + $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection()); + return $this->propagator; + } + + public function writeStream(string $path, $stream, int $size = null): int { + $storage = $this->getWrapperStorage(); + if ($storage->instanceOfStorage(IWriteStreamStorage::class)) { + /** @var IWriteStreamStorage $storage */ + return $storage->writeStream($this->getUnjailedPath($path), $stream, $size); + } else { + $target = $this->fopen($path, 'w'); + list($count, $result) = \OC_Helper::streamCopy($stream, $target); + fclose($stream); + fclose($target); + return $count; + } + } + + public function getDirectoryContent($directory): \Traversable { + return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/PermissionsMask.php new file mode 100644 index 0000000..9c2d123 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/PermissionsMask.php @@ -0,0 +1,165 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Files\Cache\Wrapper\CachePermissionsMask; +use OCP\Constants; + +/** + * Mask the permissions of a storage + * + * This can be used to restrict update, create, delete and/or share permissions of a storage + * + * Note that the read permissions can't be masked + */ +class PermissionsMask extends Wrapper { + /** + * @var int the permissions bits we want to keep + */ + private $mask; + + /** + * @param array $arguments ['storage' => $storage, 'mask' => $mask] + * + * $storage: The storage the permissions mask should be applied on + * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants + */ + public function __construct($arguments) { + parent::__construct($arguments); + $this->mask = $arguments['mask']; + } + + private function checkMask($permissions) { + return ($this->mask & $permissions) === $permissions; + } + + public function isUpdatable($path) { + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path); + } + + public function isCreatable($path) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path); + } + + public function isDeletable($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path); + } + + public function isSharable($path) { + return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path); + } + + public function getPermissions($path) { + return $this->storage->getPermissions($path) & $this->mask; + } + + public function rename($path1, $path2) { + //This is a rename of the transfer file to the original file + if (dirname($path1) === dirname($path2) && strpos($path1, '.ocTransferId') > 0) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($path1, $path2); + } + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); + } + + public function copy($path1, $path2) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($path1, $path2); + } + + public function touch($path, $mtime = null) { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) and parent::touch($path, $mtime); + } + + public function mkdir($path) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path); + } + + public function rmdir($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path); + } + + public function unlink($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path); + } + + public function file_put_contents($path, $data) { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) ? parent::file_put_contents($path, $data) : false; + } + + public function fopen($path, $mode) { + if ($mode === 'r' or $mode === 'rb') { + return parent::fopen($path, $mode); + } else { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) ? parent::fopen($path, $mode) : false; + } + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + $sourceCache = parent::getCache($path, $storage); + return new CachePermissionsMask($sourceCache, $this->mask); + } + + public function getMetaData($path) { + $data = parent::getMetaData($path); + + if ($data && isset($data['permissions'])) { + $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions']; + $data['permissions'] &= $this->mask; + } + return $data; + } + + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this->storage; + } + return parent::getScanner($path, $storage); + } + + public function getDirectoryContent($directory): \Traversable { + foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) { + $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions']; + $data['permissions'] &= $this->mask; + + yield $data; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Quota.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Quota.php new file mode 100644 index 0000000..4b99af4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Quota.php @@ -0,0 +1,239 @@ + + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Files\Filesystem; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Storage\IStorage; + +class Quota extends Wrapper { + + /** + * @var int $quota + */ + protected $quota; + + /** + * @var string $sizeRoot + */ + protected $sizeRoot; + + private $config; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + parent::__construct($parameters); + $this->quota = $parameters['quota']; + $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : ''; + $this->config = \OC::$server->getSystemConfig(); + } + + /** + * @return int quota value + */ + public function getQuota() { + return $this->quota; + } + + /** + * @param string $path + * @param \OC\Files\Storage\Storage $storage + */ + protected function getSize($path, $storage = null) { + if ($this->config->getValue('quota_include_external_storage', false)) { + $rootInfo = Filesystem::getFileInfo('', 'ext'); + return $rootInfo->getSize(true); + } else { + if (is_null($storage)) { + $cache = $this->getCache(); + } else { + $cache = $storage->getCache(); + } + $data = $cache->get($path); + if ($data instanceof ICacheEntry and isset($data['size'])) { + return $data['size']; + } else { + return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + } + } + } + + /** + * Get free space as limited by the quota + * + * @param string $path + * @return int + */ + public function free_space($path) { + if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) { + return $this->storage->free_space($path); + } else { + $used = $this->getSize($this->sizeRoot); + if ($used < 0) { + return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + } else { + $free = $this->storage->free_space($path); + $quotaFree = max($this->quota - $used, 0); + // if free space is known + if ($free >= 0) { + $free = min($free, $quotaFree); + } else { + $free = $quotaFree; + } + return $free; + } + } + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + $free = $this->free_space($path); + if ($free < 0 or strlen($data) < $free) { + return $this->storage->file_put_contents($path, $data); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $source + * @param string $target + * @return bool + */ + public function copy($source, $target) { + $free = $this->free_space($target); + if ($free < 0 or $this->getSize($source) < $free) { + return $this->storage->copy($source, $target); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $source = $this->storage->fopen($path, $mode); + + // don't apply quota for part files + if (!$this->isPartFile($path)) { + $free = $this->free_space($path); + if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { + // only apply quota for files, not metadata, trash or others + if ($this->shouldApplyQuota($path)) { + return \OC\Files\Stream\Quota::wrap($source, $free); + } + } + } + return $source; + } + + /** + * Checks whether the given path is a part file + * + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + private function isPartFile($path) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + return ($extension === 'part'); + } + + /** + * Only apply quota for files, not metadata, trash or others + */ + private function shouldApplyQuota(string $path): bool { + return strpos(ltrim($path, '/'), 'files/') === 0; + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $free = $this->free_space($targetInternalPath); + if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } else { + return false; + } + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $free = $this->free_space($targetInternalPath); + if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } else { + return false; + } + } + + public function mkdir($path) { + $free = $this->free_space($path); + if ($this->shouldApplyQuota($path) && $free === 0.0) { + return false; + } + + return parent::mkdir($path); + } + + public function touch($path, $mtime = null) { + $free = $this->free_space($path); + if ($free === 0.0) { + return false; + } + + return parent::touch($path, $mtime); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Wrapper.php b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Wrapper.php new file mode 100644 index 0000000..4584beb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -0,0 +1,644 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * @author Vinicius Cubas Brand + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OCP\Files\InvalidPathException; +use OCP\Files\Storage\ILockingStorage; +use OCP\Files\Storage\IStorage; +use OCP\Files\Storage\IWriteStreamStorage; +use OCP\Lock\ILockingProvider; + +class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + public $cache; + public $scanner; + public $watcher; + public $propagator; + public $updater; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getWrapperStorage() { + return $this->storage; + } + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + */ + public function getId() { + return $this->getWrapperStorage()->getId(); + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->getWrapperStorage()->mkdir($path); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->getWrapperStorage()->rmdir($path); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->getWrapperStorage()->opendir($path); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->getWrapperStorage()->is_dir($path); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->getWrapperStorage()->is_file($path); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->getWrapperStorage()->stat($path); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->getWrapperStorage()->filetype($path); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->getWrapperStorage()->filesize($path); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->getWrapperStorage()->isCreatable($path); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->getWrapperStorage()->isReadable($path); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->getWrapperStorage()->isUpdatable($path); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->getWrapperStorage()->isDeletable($path); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->getWrapperStorage()->isSharable($path); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->getWrapperStorage()->getPermissions($path); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->getWrapperStorage()->file_exists($path); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->getWrapperStorage()->filemtime($path); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->getWrapperStorage()->file_get_contents($path); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->getWrapperStorage()->file_put_contents($path, $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->getWrapperStorage()->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->getWrapperStorage()->rename($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->getWrapperStorage()->copy($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->getWrapperStorage()->fopen($path, $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->getWrapperStorage()->getMimeType($path); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->getWrapperStorage()->hash($type, $path, $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->getWrapperStorage()->free_space($path); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->getWrapperStorage()->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->getWrapperStorage()->touch($path, $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->getWrapperStorage()->getLocalFile($path); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->getWrapperStorage()->hasUpdated($path, $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getCache($path, $storage); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getScanner($path, $storage); + } + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->getWrapperStorage()->getOwner($path); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getWatcher($path, $storage); + } + + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getPropagator($storage); + } + + public function getUpdater($storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->getWrapperStorage()->getUpdater($storage); + } + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache() { + return $this->getWrapperStorage()->getStorageCache(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->getWrapperStorage()->getETag($path); + } + + /** + * Returns true + * + * @return true + */ + public function test() { + return $this->getWrapperStorage()->test(); + } + + /** + * Returns the wrapped storage's value for isLocal() + * + * @return bool wrapped storage's isLocal() value + */ + public function isLocal() { + return $this->getWrapperStorage()->isLocal(); + } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } + return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class); + } + + /** + * Pass any methods custom to specific storage implementations to the wrapped storage + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) { + return call_user_func_array([$this->getWrapperStorage(), $method], $args); + } + + /** + * A custom storage implementation can return an url for direct download of a give file. + * + * For now the returned array can hold the parameter url - in future more attributes might follow. + * + * @param string $path + * @return array + */ + public function getDirectDownload($path) { + return $this->getWrapperStorage()->getDirectDownload($path); + } + + /** + * Get availability of the storage + * + * @return array [ available, last_checked ] + */ + public function getAvailability() { + return $this->getWrapperStorage()->getAvailability(); + } + + /** + * Set availability of the storage + * + * @param bool $isAvailable + */ + public function setAvailability($isAvailable) { + $this->getWrapperStorage()->setAvailability($isAvailable); + } + + /** + * @param string $path the path of the target folder + * @param string $fileName the name of the file itself + * @return void + * @throws InvalidPathException + */ + public function verifyPath($path, $fileName) { + $this->getWrapperStorage()->verifyPath($path, $fileName); + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + + return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + + /** + * @param IStorage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->getWrapperStorage()->getMetaData($path); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->getWrapperStorage()->acquireLock($path, $type, $provider); + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->getWrapperStorage()->releaseLock($path, $type, $provider); + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function changeLock($path, $type, ILockingProvider $provider) { + if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->getWrapperStorage()->changeLock($path, $type, $provider); + } + } + + /** + * @return bool + */ + public function needsPartFile() { + return $this->getWrapperStorage()->needsPartFile(); + } + + public function writeStream(string $path, $stream, int $size = null): int { + $storage = $this->getWrapperStorage(); + if ($storage->instanceOfStorage(IWriteStreamStorage::class)) { + /** @var IWriteStreamStorage $storage */ + return $storage->writeStream($path, $stream, $size); + } else { + $target = $this->fopen($path, 'w'); + list($count, $result) = \OC_Helper::streamCopy($stream, $target); + fclose($stream); + fclose($target); + return $count; + } + } + + public function getDirectoryContent($directory): \Traversable { + return $this->getWrapperStorage()->getDirectoryContent($directory); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Stream/Encryption.php b/docker/overlays/nextcloud/html/lib/private/Files/Stream/Encryption.php new file mode 100644 index 0000000..980be6d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Stream/Encryption.php @@ -0,0 +1,541 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author jknockaert + * @author Lukas Reschke + * @author martink-p <47943787+martink-p@users.noreply.github.com> + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\Wrapper; +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; + +class Encryption extends Wrapper { + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OC\Encryption\File */ + protected $file; + + /** @var \OCP\Encryption\IEncryptionModule */ + protected $encryptionModule; + + /** @var \OC\Files\Storage\Storage */ + protected $storage; + + /** @var \OC\Files\Storage\Wrapper\Encryption */ + protected $encryptionStorage; + + /** @var string */ + protected $internalPath; + + /** @var string */ + protected $cache; + + /** @var integer */ + protected $size; + + /** @var integer */ + protected $position; + + /** @var integer */ + protected $unencryptedSize; + + /** @var integer */ + protected $headerSize; + + /** @var integer */ + protected $unencryptedBlockSize; + + /** @var array */ + protected $header; + + /** @var string */ + protected $fullPath; + + /** @var bool */ + protected $signed; + + /** + * header data returned by the encryption module, will be written to the file + * in case of a write operation + * + * @var array + */ + protected $newHeader; + + /** + * user who perform the read/write operation null for public access + * + * @var string + */ + protected $uid; + + /** @var bool */ + protected $readOnly; + + /** @var bool */ + protected $writeFlag; + + /** @var array */ + protected $expectedContextProperties; + + /** @var bool */ + protected $fileUpdated; + + public function __construct() { + $this->expectedContextProperties = [ + 'source', + 'storage', + 'internalPath', + 'fullPath', + 'encryptionModule', + 'header', + 'uid', + 'file', + 'util', + 'size', + 'unencryptedSize', + 'encryptionStorage', + 'headerSize', + 'signed' + ]; + } + + + /** + * Wraps a stream with the provided callbacks + * + * @param resource $source + * @param string $internalPath relative to mount point + * @param string $fullPath relative to data/ + * @param array $header + * @param string $uid + * @param \OCP\Encryption\IEncryptionModule $encryptionModule + * @param \OC\Files\Storage\Storage $storage + * @param \OC\Files\Storage\Wrapper\Encryption $encStorage + * @param \OC\Encryption\Util $util + * @param \OC\Encryption\File $file + * @param string $mode + * @param int $size + * @param int $unencryptedSize + * @param int $headerSize + * @param bool $signed + * @param string $wrapper stream wrapper class + * @return resource + * + * @throws \BadMethodCallException + */ + public static function wrap($source, $internalPath, $fullPath, array $header, + $uid, + \OCP\Encryption\IEncryptionModule $encryptionModule, + \OC\Files\Storage\Storage $storage, + \OC\Files\Storage\Wrapper\Encryption $encStorage, + \OC\Encryption\Util $util, + \OC\Encryption\File $file, + $mode, + $size, + $unencryptedSize, + $headerSize, + $signed, + $wrapper = Encryption::class) { + $context = stream_context_create([ + 'ocencryption' => [ + 'source' => $source, + 'storage' => $storage, + 'internalPath' => $internalPath, + 'fullPath' => $fullPath, + 'encryptionModule' => $encryptionModule, + 'header' => $header, + 'uid' => $uid, + 'util' => $util, + 'file' => $file, + 'size' => $size, + 'unencryptedSize' => $unencryptedSize, + 'encryptionStorage' => $encStorage, + 'headerSize' => $headerSize, + 'signed' => $signed + ] + ]); + + return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode); + } + + /** + * add stream wrapper + * + * @param resource $source + * @param string $mode + * @param resource $context + * @param string $protocol + * @param string $class + * @return resource + * @throws \BadMethodCallException + */ + protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') { + try { + stream_wrapper_register($protocol, $class); + if (self::isDirectoryHandle($source)) { + $wrapped = opendir($protocol . '://', $context); + } else { + $wrapped = fopen($protocol . '://', $mode, false, $context); + } + } catch (\BadMethodCallException $e) { + stream_wrapper_unregister($protocol); + throw $e; + } + stream_wrapper_unregister($protocol); + return $wrapped; + } + + /** + * Load the source from the stream context and return the context options + * + * @param string $name + * @return array + * @throws \BadMethodCallException + */ + protected function loadContext($name) { + $context = parent::loadContext($name); + + foreach ($this->expectedContextProperties as $property) { + if (array_key_exists($property, $context)) { + $this->{$property} = $context[$property]; + } else { + throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set'); + } + } + return $context; + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->loadContext('ocencryption'); + + $this->position = 0; + $this->cache = ''; + $this->writeFlag = false; + $this->fileUpdated = false; + $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed); + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + || $mode === 'r+' + || $mode === 'rb+' + ) { + $this->readOnly = false; + } else { + $this->readOnly = true; + } + + $sharePath = $this->fullPath; + if (!$this->storage->file_exists($this->internalPath)) { + $sharePath = dirname($sharePath); + } + + $accessList = []; + if ($this->encryptionModule->needDetailedAccessList()) { + $accessList = $this->file->getAccessList($sharePath); + } + $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList); + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + // We're writing a new file so start write counter with 0 bytes + $this->unencryptedSize = 0; + $this->writeHeader(); + $this->headerSize = $this->util->getHeaderSize(); + $this->size = $this->headerSize; + } else { + $this->skipHeader(); + } + + return true; + } + + public function stream_eof() { + return $this->position >= $this->unencryptedSize; + } + + public function stream_read($count) { + $result = ''; + + $count = min($count, $this->unencryptedSize - $this->position); + while ($count > 0) { + $remainingLength = $count; + // update the cache of the current block + $this->readCache(); + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // if entire read inside current block then only position needs to be updated + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $result .= substr($this->cache, $blockPosition, $remainingLength); + $this->position += $remainingLength; + $count = 0; + // otherwise remainder of current block is fetched, the block is flushed and the position updated + } else { + $result .= substr($this->cache, $blockPosition); + $this->flush(); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $count -= ($this->unencryptedBlockSize - $blockPosition); + } + } + return $result; + } + + /** + * stream_read_block + * + * This function is a wrapper for function stream_read. + * It calls stream read until the requested $blockSize was received or no remaining data is present. + * This is required as stream_read only returns smaller chunks of data when the stream fetches from a + * remote storage over the internet and it does not care about the given $blockSize. + * + * @param int $blockSize Length of requested data block in bytes + * @return string Data fetched from stream. + */ + private function stream_read_block(int $blockSize): string { + $remaining = $blockSize; + $data = ''; + + do { + $chunk = parent::stream_read($remaining); + $chunk_len = strlen($chunk); + $data .= $chunk; + $remaining -= $chunk_len; + } while (($remaining > 0) && ($chunk_len > 0)); + + return $data; + } + + public function stream_write($data) { + $length = 0; + // loop over $data to fit it in 6126 sized unencrypted blocks + while (isset($data[0])) { + $remainingLength = strlen($data); + + // set the cache to the current 6126 block + $this->readCache(); + + // for seekable streams the pointer is moved back to the beginning of the encrypted block + // flush will start writing there when the position moves to another block + $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) * + $this->util->getBlockSize() + $this->headerSize; + $resultFseek = $this->parentStreamSeek($positionInFile); + + // only allow writes on seekable streams, or at the end of the encrypted stream + if (!$this->readOnly && ($resultFseek || $positionInFile === $this->size)) { + + // switch the writeFlag so flush() will write the block + $this->writeFlag = true; + $this->fileUpdated = true; + + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // check if $data fits in current block + // if so, overwrite existing data (if any) + // update position and liberate $data + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $this->cache = substr($this->cache, 0, $blockPosition) + . $data . substr($this->cache, $blockPosition + $remainingLength); + $this->position += $remainingLength; + $length += $remainingLength; + $data = ''; + // if $data doesn't fit the current block, the fill the current block and reiterate + // after the block is filled, it is flushed and $data is updatedxxx + } else { + $this->cache = substr($this->cache, 0, $blockPosition) . + substr($data, 0, $this->unencryptedBlockSize - $blockPosition); + $this->flush(); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $length += ($this->unencryptedBlockSize - $blockPosition); + $data = substr($data, $this->unencryptedBlockSize - $blockPosition); + } + } else { + $data = ''; + } + $this->unencryptedSize = max($this->unencryptedSize, $this->position); + } + return $length; + } + + public function stream_tell() { + return $this->position; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + $return = false; + + switch ($whence) { + case SEEK_SET: + $newPosition = $offset; + break; + case SEEK_CUR: + $newPosition = $this->position + $offset; + break; + case SEEK_END: + $newPosition = $this->unencryptedSize + $offset; + break; + default: + return $return; + } + + if ($newPosition > $this->unencryptedSize || $newPosition < 0) { + return $return; + } + + $newFilePosition = (int)floor($newPosition / $this->unencryptedBlockSize) + * $this->util->getBlockSize() + $this->headerSize; + + $oldFilePosition = parent::stream_tell(); + if ($this->parentStreamSeek($newFilePosition)) { + $this->parentStreamSeek($oldFilePosition); + $this->flush(); + $this->parentStreamSeek($newFilePosition); + $this->position = $newPosition; + $return = true; + } + return $return; + } + + public function stream_close() { + $this->flush('end'); + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end'); + if ($this->readOnly === false) { + if (!empty($remainingData)) { + parent::stream_write($remainingData); + } + $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize); + } + $result = parent::stream_close(); + + if ($this->fileUpdated) { + $cache = $this->storage->getCache(); + $cacheEntry = $cache->get($this->internalPath); + if ($cacheEntry) { + $version = $cacheEntry['encryptedVersion'] + 1; + $cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]); + } + } + + return $result; + } + + /** + * write block to file + * @param string $positionPrefix + */ + protected function flush($positionPrefix = '') { + // write to disk only when writeFlag was set to 1 + if ($this->writeFlag) { + // Disable the file proxies so that encryption is not + // automatically attempted when the file is written to disk - + // we are handling that separately here and we don't want to + // get into an infinite loop + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix); + $bytesWritten = parent::stream_write($encrypted); + $this->writeFlag = false; + // Check whether the write concerns the last block + // If so then update the encrypted filesize + // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called + // We recalculate the encrypted filesize as we do not know the context of calling flush() + $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize); + if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) { + $this->size = $this->util->getBlockSize() * $completeBlocksInFile; + $this->size += $bytesWritten; + $this->size += $this->headerSize; + } + } + // always empty the cache (otherwise readCache() will not fill it with the new block) + $this->cache = ''; + } + + /** + * read block to file + */ + protected function readCache() { + // cache should always be empty string when this function is called + // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block + if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) { + // Get the data from the file handle + $data = $this->stream_read_block($this->util->getBlockSize()); + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize); + if ($numberOfChunks === $position) { + $position .= 'end'; + } + $this->cache = $this->encryptionModule->decrypt($data, $position); + } + } + + /** + * write header at beginning of encrypted file + * + * @return integer + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + protected function writeHeader() { + $header = $this->util->createHeader($this->newHeader, $this->encryptionModule); + return parent::stream_write($header); + } + + /** + * read first block to skip the header + */ + protected function skipHeader() { + $this->stream_read_block($this->headerSize); + } + + /** + * call stream_seek() from parent class + * + * @param integer $position + * @return bool + */ + protected function parentStreamSeek($position) { + return parent::stream_seek($position); + } + + /** + * @param string $path + * @param array $options + * @return bool + */ + public function dir_opendir($path, $options) { + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Stream/HashWrapper.php b/docker/overlays/nextcloud/html/lib/private/Files/Stream/HashWrapper.php new file mode 100644 index 0000000..059dd11 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Stream/HashWrapper.php @@ -0,0 +1,77 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\Wrapper; + +class HashWrapper extends Wrapper { + protected $callback; + protected $hash; + + public static function wrap($source, string $algo, callable $callback) { + $hash = hash_init($algo); + $context = stream_context_create([ + 'hash' => [ + 'source' => $source, + 'callback' => $callback, + 'hash' => $hash, + ], + ]); + return Wrapper::wrapSource($source, $context, 'hash', self::class); + } + + protected function open() { + $context = $this->loadContext('hash'); + + $this->callback = $context['callback']; + $this->hash = $context['hash']; + return true; + } + + public function dir_opendir($path, $options) { + return $this->open(); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + return $this->open(); + } + + public function stream_read($count) { + $result = parent::stream_read($count); + hash_update($this->hash, $result); + return $result; + } + + public function stream_close() { + if (is_callable($this->callback)) { + call_user_func($this->callback, hash_final($this->hash)); + // prevent further calls by potential PHP 7 GC ghosts + $this->callback = null; + } + return parent::stream_close(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Stream/Quota.php b/docker/overlays/nextcloud/html/lib/private/Files/Stream/Quota.php new file mode 100644 index 0000000..3bf4626 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Stream/Quota.php @@ -0,0 +1,103 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\Wrapper; + +/** + * stream wrapper limits the amount of data that can be written to a stream + * + * usage: resource \OC\Files\Stream\Quota::wrap($stream, $limit) + */ +class Quota extends Wrapper { + /** + * @var int $limit + */ + private $limit; + + /** + * @param resource $stream + * @param int $limit + * @return resource + */ + public static function wrap($stream, $limit) { + $context = stream_context_create([ + 'quota' => [ + 'source' => $stream, + 'limit' => $limit + ] + ]); + return Wrapper::wrapSource($stream, $context, 'quota', self::class); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $context = $this->loadContext('quota'); + $this->source = $context['source']; + $this->limit = $context['limit']; + + return true; + } + + public function dir_opendir($path, $options) { + return false; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + if ($whence === SEEK_END) { + // go to the end to find out last position's offset + $oldOffset = $this->stream_tell(); + if (fseek($this->source, 0, $whence) !== 0) { + return false; + } + $whence = SEEK_SET; + $offset = $this->stream_tell() + $offset; + $this->limit += $oldOffset - $offset; + } elseif ($whence === SEEK_SET) { + $this->limit += $this->stream_tell() - $offset; + } else { + $this->limit -= $offset; + } + // this wrapper needs to return "true" for success. + // the fseek call itself returns 0 on succeess + return fseek($this->source, $offset, $whence) === 0; + } + + public function stream_read($count) { + $this->limit -= $count; + return fread($this->source, $count); + } + + public function stream_write($data) { + $size = strlen($data); + if ($size > $this->limit) { + $data = substr($data, 0, $this->limit); + $size = $this->limit; + } + $this->limit -= $size; + return fwrite($this->source, $data); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Stream/SeekableHttpStream.php b/docker/overlays/nextcloud/html/lib/private/Files/Stream/SeekableHttpStream.php new file mode 100644 index 0000000..efbfd29 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Stream/SeekableHttpStream.php @@ -0,0 +1,194 @@ + + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\File; + +/** + * A stream wrapper that uses http range requests to provide a seekable stream for http reading + */ +class SeekableHttpStream implements File { + private const PROTOCOL = 'httpseek'; + + private static $registered = false; + + /** + * Registers the stream wrapper using the `httpseek://` url scheme + * $return void + */ + private static function registerIfNeeded() { + if (!self::$registered) { + stream_wrapper_register( + self::PROTOCOL, + self::class + ); + self::$registered = true; + } + } + + /** + * Open a readonly-seekable http stream + * + * The provided callback will be called with byte range and should return an http stream for the requested range + * + * @param callable $callback + * @return false|resource + */ + public static function open(callable $callback) { + $context = stream_context_create([ + SeekableHttpStream::PROTOCOL => [ + 'callback' => $callback + ], + ]); + + SeekableHttpStream::registerIfNeeded(); + return fopen(SeekableHttpStream::PROTOCOL . '://', 'r', false, $context); + } + + /** @var resource */ + public $context; + + /** @var callable */ + private $openCallback; + + /** @var resource */ + private $current; + /** @var int */ + private $offset = 0; + + private function reconnect(int $start) { + $range = $start . '-'; + if ($this->current != null) { + fclose($this->current); + } + + $this->current = ($this->openCallback)($range); + + if ($this->current === false) { + return false; + } + + $responseHead = stream_get_meta_data($this->current)['wrapper_data']; + $rangeHeaders = array_values(array_filter($responseHead, function ($v) { + return preg_match('#^content-range:#i', $v) === 1; + })); + if (!$rangeHeaders) { + return false; + } + $contentRange = $rangeHeaders[0]; + + $content = trim(explode(':', $contentRange)[1]); + $range = trim(explode(' ', $content)[1]); + $begin = intval(explode('-', $range)[0]); + + if ($begin !== $start) { + return false; + } + + $this->offset = $begin; + + return true; + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $options = stream_context_get_options($this->context)[self::PROTOCOL]; + $this->openCallback = $options['callback']; + + return $this->reconnect(0); + } + + public function stream_read($count) { + if (!$this->current) { + return false; + } + $ret = fread($this->current, $count); + $this->offset += strlen($ret); + return $ret; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + switch ($whence) { + case SEEK_SET: + if ($offset === $this->offset) { + return true; + } + return $this->reconnect($offset); + case SEEK_CUR: + if ($offset === 0) { + return true; + } + return $this->reconnect($this->offset + $offset); + case SEEK_END: + return false; + } + return false; + } + + public function stream_tell() { + return $this->offset; + } + + public function stream_stat() { + if (is_resource($this->current)) { + return fstat($this->current); + } else { + return false; + } + } + + public function stream_eof() { + if (is_resource($this->current)) { + return feof($this->current); + } else { + return true; + } + } + + public function stream_close() { + if (is_resource($this->current)) { + fclose($this->current); + } + } + + public function stream_write($data) { + return false; + } + + public function stream_set_option($option, $arg1, $arg2) { + return false; + } + + public function stream_truncate($size) { + return false; + } + + public function stream_lock($operation) { + return false; + } + + public function stream_flush() { + return; //noop because readonly stream + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Type/Detection.php b/docker/overlays/nextcloud/html/lib/private/Files/Type/Detection.php new file mode 100644 index 0000000..f8cbd2e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Type/Detection.php @@ -0,0 +1,398 @@ + + * @author bladewing + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Hendrik Leppelsack + * @author Jens-Christian Fischer + * @author Joas Schilling + * @author lui87kw + * @author Lukas Reschke + * @author Magnus Walbeck + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Tanghus + * @author Vincent Petry + * @author Xheni Myrtaj + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +use OCP\Files\IMimeTypeDetector; +use OCP\ILogger; +use OCP\IURLGenerator; + +/** + * Class Detection + * + * Mimetype detection + * + * @package OC\Files\Type + */ +class Detection implements IMimeTypeDetector { + private const CUSTOM_MIMETYPEMAPPING = 'mimetypemapping.json'; + private const CUSTOM_MIMETYPEALIASES = 'mimetypealiases.json'; + + protected $mimetypes = []; + protected $secureMimeTypes = []; + + protected $mimetypeIcons = []; + /** @var string[] */ + protected $mimeTypeAlias = []; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var ILogger */ + private $logger; + + /** @var string */ + private $customConfigDir; + + /** @var string */ + private $defaultConfigDir; + + /** + * @param IURLGenerator $urlGenerator + * @param ILogger $logger + * @param string $customConfigDir + * @param string $defaultConfigDir + */ + public function __construct(IURLGenerator $urlGenerator, + ILogger $logger, + string $customConfigDir, + string $defaultConfigDir) { + $this->urlGenerator = $urlGenerator; + $this->logger = $logger; + $this->customConfigDir = $customConfigDir; + $this->defaultConfigDir = $defaultConfigDir; + } + + /** + * Add an extension -> mimetype mapping + * + * $mimetype is the assumed correct mime type + * The optional $secureMimeType is an alternative to send to send + * to avoid potential XSS. + * + * @param string $extension + * @param string $mimetype + * @param string|null $secureMimeType + */ + public function registerType(string $extension, + string $mimetype, + ?string $secureMimeType = null): void { + $this->mimetypes[$extension] = [$mimetype, $secureMimeType]; + $this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype; + } + + /** + * Add an array of extension -> mimetype mappings + * + * The mimetype value is in itself an array where the first index is + * the assumed correct mimetype and the second is either a secure alternative + * or null if the correct is considered secure. + * + * @param array $types + */ + public function registerTypeArray(array $types): void { + $this->mimetypes = array_merge($this->mimetypes, $types); + + // Update the alternative mimetypes to avoid having to look them up each time. + foreach ($this->mimetypes as $extension => $mimeType) { + if (strpos($extension, '_comment') === 0) { + continue; + } + $this->secureMimeTypes[$mimeType[0]] = $mimeType[1] ?? $mimeType[0]; + if (isset($mimeType[1])) { + $this->secureMimeTypes[$mimeType[1]] = $mimeType[1]; + } + } + } + + private function loadCustomDefinitions(string $fileName, array $definitions): array { + if (file_exists($this->customConfigDir . '/' . $fileName)) { + $custom = json_decode(file_get_contents($this->customConfigDir . '/' . $fileName), true); + if (json_last_error() === JSON_ERROR_NONE) { + $definitions = array_merge($definitions, $custom); + } else { + $this->logger->warning('Failed to parse ' . $fileName . ': ' . json_last_error_msg()); + } + } + return $definitions; + } + + /** + * Add the mimetype aliases if they are not yet present + */ + private function loadAliases(): void { + if (!empty($this->mimeTypeAlias)) { + return; + } + + $this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true); + $this->mimeTypeAlias = $this->loadCustomDefinitions(self::CUSTOM_MIMETYPEALIASES, $this->mimeTypeAlias); + } + + /** + * @return string[] + */ + public function getAllAliases(): array { + $this->loadAliases(); + return $this->mimeTypeAlias; + } + + public function getOnlyDefaultAliases(): array { + $this->loadMappings(); + $this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true); + return $this->mimeTypeAlias; + } + + /** + * Add mimetype mappings if they are not yet present + */ + private function loadMappings(): void { + if (!empty($this->mimetypes)) { + return; + } + + $mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true); + $mimetypeMapping = $this->loadCustomDefinitions(self::CUSTOM_MIMETYPEMAPPING, $mimetypeMapping); + + $this->registerTypeArray($mimetypeMapping); + } + + /** + * @return array + */ + public function getAllMappings(): array { + $this->loadMappings(); + return $this->mimetypes; + } + + /** + * detect mimetype only based on filename, content of file is not used + * + * @param string $path + * @return string + */ + public function detectPath($path): string { + $this->loadMappings(); + + $fileName = basename($path); + + // remove leading dot on hidden files with a file extension + $fileName = ltrim($fileName, '.'); + + // note: leading dot doesn't qualify as extension + if (strpos($fileName, '.') > 0) { + + // remove versioning extension: name.v1508946057 and transfer extension: name.ocTransferId2057600214.part + $fileName = preg_replace('!((\.v\d+)|((\.ocTransferId\d+)?\.part))$!', '', $fileName); + + //try to guess the type by the file extension + $extension = strrchr($fileName, '.'); + if ($extension !== false) { + $extension = strtolower($extension); + $extension = substr($extension, 1); //remove leading . + return $this->mimetypes[$extension][0] ?? 'application/octet-stream'; + } + } + + return 'application/octet-stream'; + } + + /** + * detect mimetype only based on the content of file + * @param string $path + * @return string + * @since 18.0.0 + */ + public function detectContent(string $path): string { + $this->loadMappings(); + + if (@is_dir($path)) { + // directories are easy + return 'httpd/unix-directory'; + } + + if (function_exists('finfo_open') + && function_exists('finfo_file') + && $finfo = finfo_open(FILEINFO_MIME)) { + $info = @finfo_file($finfo, $path); + finfo_close($finfo); + if ($info) { + $info = strtolower($info); + $mimeType = strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info; + $mimeType = $this->getSecureMimeType($mimeType); + if ($mimeType !== 'application/octet-stream') { + return $mimeType; + } + } + } + + if (strpos($path, '://') !== false && strpos($path, 'file://') === 0) { + // Is the file wrapped in a stream? + return 'application/octet-stream'; + } + + if (function_exists('mime_content_type')) { + // use mime magic extension if available + $mimeType = mime_content_type($path); + if ($mimeType !== false) { + $mimeType = $this->getSecureMimeType($mimeType); + if ($mimeType !== 'application/octet-stream') { + return $mimeType; + } + } + } + + if (\OC_Helper::canExecute('file')) { + // it looks like we have a 'file' command, + // lets see if it does have mime support + $path = escapeshellarg($path); + $fp = popen("test -f $path && file -b --mime-type $path", 'r'); + $mimeType = fgets($fp); + pclose($fp); + + if ($mimeType !== false) { + //trim the newline + $mimeType = trim($mimeType); + $mimeType = $this->getSecureMimeType($mimeType); + if ($mimeType !== 'application/octet-stream') { + return $mimeType; + } + } + } + return 'application/octet-stream'; + } + + /** + * detect mimetype based on both filename and content + * + * @param string $path + * @return string + */ + public function detect($path): string { + $mimeType = $this->detectPath($path); + + if ($mimeType !== 'application/octet-stream') { + return $mimeType; + } + + return $this->detectContent($path); + } + + /** + * detect mimetype based on the content of a string + * + * @param string $data + * @return string + */ + public function detectString($data): string { + if (function_exists('finfo_open') && function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME); + $info = finfo_buffer($finfo, $data); + return strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info; + } + + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + $fh = fopen($tmpFile, 'wb'); + fwrite($fh, $data, 8024); + fclose($fh); + $mime = $this->detect($tmpFile); + unset($tmpFile); + return $mime; + } + + /** + * Get a secure mimetype that won't expose potential XSS. + * + * @param string $mimeType + * @return string + */ + public function getSecureMimeType($mimeType): string { + $this->loadMappings(); + + return $this->secureMimeTypes[$mimeType] ?? 'application/octet-stream'; + } + + /** + * Get path to the icon of a file type + * @param string $mimetype the MIME type + * @return string the url + */ + public function mimeTypeIcon($mimetype): string { + $this->loadAliases(); + + while (isset($this->mimeTypeAlias[$mimetype])) { + $mimetype = $this->mimeTypeAlias[$mimetype]; + } + if (isset($this->mimetypeIcons[$mimetype])) { + return $this->mimetypeIcons[$mimetype]; + } + + // Replace slash and backslash with a minus + $icon = str_replace(['/', '\\'], '-', $mimetype); + + // Is it a dir? + if ($mimetype === 'dir') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg'); + return $this->mimetypeIcons[$mimetype]; + } + if ($mimetype === 'dir-shared') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg'); + return $this->mimetypeIcons[$mimetype]; + } + if ($mimetype === 'dir-external') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg'); + return $this->mimetypeIcons[$mimetype]; + } + + // Icon exists? + try { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg'); + return $this->mimetypeIcons[$mimetype]; + } catch (\RuntimeException $e) { + // Specified image not found + } + + // Try only the first part of the filetype + + if (strpos($icon, '-')) { + $mimePart = substr($icon, 0, strpos($icon, '-')); + try { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg'); + return $this->mimetypeIcons[$mimetype]; + } catch (\RuntimeException $e) { + // Image for the first part of the mimetype not found + } + } + + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg'); + return $this->mimetypeIcons[$mimetype]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Type/Loader.php b/docker/overlays/nextcloud/html/lib/private/Files/Type/Loader.php new file mode 100644 index 0000000..d128bc7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Type/Loader.php @@ -0,0 +1,176 @@ + + * @author Rello + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +use OCP\Files\IMimeTypeLoader; +use OCP\IDBConnection; + +/** + * Mimetype database loader + * + * @package OC\Files\Type + */ +class Loader implements IMimeTypeLoader { + + /** @var IDBConnection */ + private $dbConnection; + + /** @var array [id => mimetype] */ + protected $mimetypes; + + /** @var array [mimetype => id] */ + protected $mimetypeIds; + + /** + * @param IDBConnection $dbConnection + */ + public function __construct(IDBConnection $dbConnection) { + $this->dbConnection = $dbConnection; + $this->mimetypes = []; + $this->mimetypeIds = []; + } + + /** + * Get a mimetype from its ID + * + * @param int $id + * @return string|null + */ + public function getMimetypeById($id) { + if (!$this->mimetypes) { + $this->loadMimetypes(); + } + if (isset($this->mimetypes[$id])) { + return $this->mimetypes[$id]; + } + return null; + } + + /** + * Get a mimetype ID, adding the mimetype to the DB if it does not exist + * + * @param string $mimetype + * @return int + */ + public function getId($mimetype) { + if (!$this->mimetypeIds) { + $this->loadMimetypes(); + } + if (isset($this->mimetypeIds[$mimetype])) { + return $this->mimetypeIds[$mimetype]; + } + return $this->store($mimetype); + } + + /** + * Test if a mimetype exists in the database + * + * @param string $mimetype + * @return bool + */ + public function exists($mimetype) { + if (!$this->mimetypeIds) { + $this->loadMimetypes(); + } + return isset($this->mimetypeIds[$mimetype]); + } + + /** + * Clear all loaded mimetypes, allow for re-loading + */ + public function reset() { + $this->mimetypes = []; + $this->mimetypeIds = []; + } + + /** + * Store a mimetype in the DB + * + * @param string $mimetype + * @param int inserted ID + */ + protected function store($mimetype) { + $this->dbConnection->insertIfNotExist('*PREFIX*mimetypes', [ + 'mimetype' => $mimetype + ]); + + $fetch = $this->dbConnection->getQueryBuilder(); + $fetch->select('id') + ->from('mimetypes') + ->where( + $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype) + )); + $row = $fetch->execute()->fetch(); + + if (!$row) { + throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it"); + } + + $this->mimetypes[$row['id']] = $mimetype; + $this->mimetypeIds[$mimetype] = $row['id']; + return $row['id']; + } + + /** + * Load all mimetypes from DB + */ + private function loadMimetypes() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('id', 'mimetype') + ->from('mimetypes'); + $results = $qb->execute()->fetchAll(); + + foreach ($results as $row) { + $this->mimetypes[$row['id']] = $row['mimetype']; + $this->mimetypeIds[$row['mimetype']] = $row['id']; + } + } + + /** + * Update filecache mimetype based on file extension + * + * @param string $ext file extension + * @param int $mimeTypeId + * @return int number of changed rows + */ + public function updateFilecache($ext, $mimeTypeId) { + $folderMimeTypeId = $this->getId('httpd/unix-directory'); + $update = $this->dbConnection->getQueryBuilder(); + $update->update('filecache') + ->set('mimetype', $update->createNamedParameter($mimeTypeId)) + ->where($update->expr()->neq( + 'mimetype', $update->createNamedParameter($mimeTypeId) + )) + ->andWhere($update->expr()->neq( + 'mimetype', $update->createNamedParameter($folderMimeTypeId) + )) + ->andWhere($update->expr()->like( + $update->func()->lower('name'), + $update->createNamedParameter('%' . $this->dbConnection->escapeLikeParameter('.' . $ext)) + )); + return $update->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Type/TemplateManager.php b/docker/overlays/nextcloud/html/lib/private/Files/Type/TemplateManager.php new file mode 100644 index 0000000..5cd6b61 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Type/TemplateManager.php @@ -0,0 +1,69 @@ + + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +/** + * @deprecated 18.0.0 + */ +class TemplateManager { + protected $templates = []; + + public function registerTemplate($mimetype, $path) { + $this->templates[$mimetype] = $path; + } + + /** + * get the path of the template for a mimetype + * + * @deprecated 18.0.0 + * @param string $mimetype + * @return string|null + */ + public function getTemplatePath($mimetype) { + if (isset($this->templates[$mimetype])) { + return $this->templates[$mimetype]; + } else { + return null; + } + } + + /** + * get the template content for a mimetype + * + * @deprecated 18.0.0 + * @param string $mimetype + * @return string + */ + public function getTemplate($mimetype) { + $path = $this->getTemplatePath($mimetype); + if ($path) { + return file_get_contents($path); + } else { + return ''; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/Utils/Scanner.php b/docker/overlays/nextcloud/html/lib/private/Files/Utils/Scanner.php new file mode 100644 index 0000000..9963800 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/Utils/Scanner.php @@ -0,0 +1,296 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Utils; + +use OC\Files\Cache\Cache; +use OC\Files\Filesystem; +use OC\Files\Storage\FailedStorage; +use OC\ForbiddenException; +use OC\Hooks\PublicEmitter; +use OC\Lock\DBLockingProvider; +use OCA\Files_Sharing\SharedStorage; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\BeforeFileScannedEvent; +use OCP\Files\Events\BeforeFolderScannedEvent; +use OCP\Files\Events\NodeAddedToCache; +use OCP\Files\Events\FileCacheUpdated; +use OCP\Files\Events\NodeRemovedFromCache; +use OCP\Files\Events\FileScannedEvent; +use OCP\Files\Events\FolderScannedEvent; +use OCP\Files\NotFoundException; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageNotAvailableException; +use OCP\IDBConnection; +use OCP\ILogger; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Utils\Scanner + * - scanFile(string $absolutePath) + * - scanFolder(string $absolutePath) + * + * @package OC\Files\Utils + */ +class Scanner extends PublicEmitter { + public const MAX_ENTRIES_TO_COMMIT = 10000; + + /** @var string $user */ + private $user; + + /** @var IDBConnection */ + protected $db; + + /** @var IEventDispatcher */ + private $dispatcher; + + /** @var ILogger */ + protected $logger; + + /** + * Whether to use a DB transaction + * + * @var bool + */ + protected $useTransaction; + + /** + * Number of entries scanned to commit + * + * @var int + */ + protected $entriesToCommit; + + /** + * @param string $user + * @param IDBConnection|null $db + * @param IEventDispatcher $dispatcher + * @param ILogger $logger + */ + public function __construct($user, $db, IEventDispatcher $dispatcher, ILogger $logger) { + $this->user = $user; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->logger = $logger; + // when DB locking is used, no DB transactions will be used + $this->useTransaction = !(\OC::$server->getLockingProvider() instanceof DBLockingProvider); + } + + /** + * get all storages for $dir + * + * @param string $dir + * @return \OC\Files\Mount\MountPoint[] + */ + protected function getMounts($dir) { + //TODO: move to the node based fileapi once that's done + \OC_Util::tearDownFS(); + \OC_Util::setupFS($this->user); + + $mountManager = Filesystem::getMountManager(); + $mounts = $mountManager->findIn($dir); + $mounts[] = $mountManager->find($dir); + $mounts = array_reverse($mounts); //start with the mount of $dir + + return $mounts; + } + + /** + * attach listeners to the scanner + * + * @param \OC\Files\Mount\MountPoint $mount + */ + protected function attachListener($mount) { + $scanner = $mount->getStorage()->getScanner(); + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount) { + $this->emit('\OC\Files\Utils\Scanner', 'scanFile', [$mount->getMountPoint() . $path]); + $this->dispatcher->dispatchTyped(new BeforeFileScannedEvent($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function ($path) use ($mount) { + $this->emit('\OC\Files\Utils\Scanner', 'scanFolder', [$mount->getMountPoint() . $path]); + $this->dispatcher->dispatchTyped(new BeforeFolderScannedEvent($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFile', function ($path) use ($mount) { + $this->emit('\OC\Files\Utils\Scanner', 'postScanFile', [$mount->getMountPoint() . $path]); + $this->dispatcher->dispatchTyped(new FileScannedEvent($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFolder', function ($path) use ($mount) { + $this->emit('\OC\Files\Utils\Scanner', 'postScanFolder', [$mount->getMountPoint() . $path]); + $this->dispatcher->dispatchTyped(new FolderScannedEvent($mount->getMountPoint() . $path)); + }); + } + + /** + * @param string $dir + */ + public function backgroundScan($dir) { + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if (is_null($storage)) { + continue; + } + + // don't bother scanning failed storages (shortcut for same result) + if ($storage->instanceOfStorage(FailedStorage::class)) { + continue; + } + + // don't scan received local shares, these can be scanned when scanning the owner's storage + if ($storage->instanceOfStorage(SharedStorage::class)) { + continue; + } + $scanner = $storage->getScanner(); + $this->attachListener($mount); + + $scanner->listen('\OC\Files\Cache\Scanner', 'removeFromCache', function ($path) use ($storage) { + $this->triggerPropagator($storage, $path); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'updateCache', function ($path) use ($storage) { + $this->triggerPropagator($storage, $path); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) { + $this->triggerPropagator($storage, $path); + }); + + $propagator = $storage->getPropagator(); + $propagator->beginBatch(); + $scanner->backgroundScan(); + $propagator->commitBatch(); + } + } + + /** + * @param string $dir + * @param $recursive + * @param callable|null $mountFilter + * @throws ForbiddenException + * @throws NotFoundException + */ + public function scan($dir = '', $recursive = \OC\Files\Cache\Scanner::SCAN_RECURSIVE, callable $mountFilter = null) { + if (!Filesystem::isValidPath($dir)) { + throw new \InvalidArgumentException('Invalid path to scan'); + } + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + if ($mountFilter && !$mountFilter($mount)) { + continue; + } + $storage = $mount->getStorage(); + if (is_null($storage)) { + continue; + } + + // don't bother scanning failed storages (shortcut for same result) + if ($storage->instanceOfStorage(FailedStorage::class)) { + continue; + } + + // if the home storage isn't writable then the scanner is run as the wrong user + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') and + (!$storage->isCreatable('') or !$storage->isCreatable('files')) + ) { + if ($storage->file_exists('') or $storage->getCache()->inCache('')) { + throw new ForbiddenException(); + } else {// if the root exists in neither the cache nor the storage the user isn't setup yet + break; + } + } + + // don't scan received local shares, these can be scanned when scanning the owner's storage + if ($storage->instanceOfStorage(SharedStorage::class)) { + continue; + } + $relativePath = $mount->getInternalPath($dir); + $scanner = $storage->getScanner(); + $scanner->setUseTransactions(false); + $this->attachListener($mount); + + $scanner->listen('\OC\Files\Cache\Scanner', 'removeFromCache', function ($path) use ($storage) { + $this->postProcessEntry($storage, $path); + $this->dispatcher->dispatchTyped(new NodeRemovedFromCache($storage, $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'updateCache', function ($path) use ($storage) { + $this->postProcessEntry($storage, $path); + $this->dispatcher->dispatchTyped(new FileCacheUpdated($storage, $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) { + $this->postProcessEntry($storage, $path); + $this->dispatcher->dispatchTyped(new NodeAddedToCache($storage, $path)); + }); + + if (!$storage->file_exists($relativePath)) { + throw new NotFoundException($dir); + } + + if ($this->useTransaction) { + $this->db->beginTransaction(); + } + try { + $propagator = $storage->getPropagator(); + $propagator->beginBatch(); + $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); + $cache = $storage->getCache(); + if ($cache instanceof Cache) { + // only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner + $cache->correctFolderSize($relativePath); + } + $propagator->commitBatch(); + } catch (StorageNotAvailableException $e) { + $this->logger->error('Storage ' . $storage->getId() . ' not available'); + $this->logger->logException($e); + $this->emit('\OC\Files\Utils\Scanner', 'StorageNotAvailable', [$e]); + } + if ($this->useTransaction) { + $this->db->commit(); + } + } + } + + private function triggerPropagator(IStorage $storage, $internalPath) { + $storage->getPropagator()->propagateChange($internalPath, time()); + } + + private function postProcessEntry(IStorage $storage, $internalPath) { + $this->triggerPropagator($storage, $internalPath); + if ($this->useTransaction) { + $this->entriesToCommit++; + if ($this->entriesToCommit >= self::MAX_ENTRIES_TO_COMMIT) { + $propagator = $storage->getPropagator(); + $this->entriesToCommit = 0; + $this->db->commit(); + $propagator->commitBatch(); + $this->db->beginTransaction(); + $propagator->beginBatch(); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Files/View.php b/docker/overlays/nextcloud/html/lib/private/Files/View.php new file mode 100644 index 0000000..16074a8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Files/View.php @@ -0,0 +1,2200 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Christoph Wurst + * @author Florin Peter + * @author Jesús Macias + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author karakayasemi + * @author Klaas Freitag + * @author korelstar + * @author Lukas Reschke + * @author Luke Policinski + * @author Michael Gapczynski + * @author Morris Jobke + * @author Piotr Filiciak + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Scott Dutton + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files; + +use Icewind\Streams\CallbackWrapper; +use OC\Files\Mount\MoveableMount; +use OC\Files\Storage\Storage; +use OC\User\User; +use OCA\Files_Sharing\SharedMount; +use OCP\Constants; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\EmptyFileNameException; +use OCP\Files\FileNameTooLongException; +use OCP\Files\InvalidCharacterInPathException; +use OCP\Files\InvalidDirectoryException; +use OCP\Files\InvalidPathException; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; +use OCP\Files\ReservedWordException; +use OCP\Files\Storage\IStorage; +use OCP\ILogger; +use OCP\IUser; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +/** + * Class to provide access to ownCloud filesystem via a "view", and methods for + * working with files within that view (e.g. read, write, delete, etc.). Each + * view is restricted to a set of directories via a virtual root. The default view + * uses the currently logged in user's data directory as root (parts of + * OC_Filesystem are merely a wrapper for OC\Files\View). + * + * Apps that need to access files outside of the user data folders (to modify files + * belonging to a user other than the one currently logged in, for example) should + * use this class directly rather than using OC_Filesystem, or making use of PHP's + * built-in file manipulation functions. This will ensure all hooks and proxies + * are triggered correctly. + * + * Filesystem functions are not called directly; they are passed to the correct + * \OC\Files\Storage\Storage object + */ +class View { + /** @var string */ + private $fakeRoot = ''; + + /** + * @var \OCP\Lock\ILockingProvider + */ + protected $lockingProvider; + + private $lockingEnabled; + + private $updaterEnabled = true; + + /** @var \OC\User\Manager */ + private $userManager; + + /** @var \OCP\ILogger */ + private $logger; + + /** + * @param string $root + * @throws \Exception If $root contains an invalid path + */ + public function __construct($root = '') { + if (is_null($root)) { + throw new \InvalidArgumentException('Root can\'t be null'); + } + if (!Filesystem::isValidPath($root)) { + throw new \Exception(); + } + + $this->fakeRoot = $root; + $this->lockingProvider = \OC::$server->getLockingProvider(); + $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider); + $this->userManager = \OC::$server->getUserManager(); + $this->logger = \OC::$server->getLogger(); + } + + public function getAbsolutePath($path = '/') { + if ($path === null) { + return null; + } + $this->assertPathLength($path); + if ($path === '') { + $path = '/'; + } + if ($path[0] !== '/') { + $path = '/' . $path; + } + return $this->fakeRoot . $path; + } + + /** + * change the root to a fake root + * + * @param string $fakeRoot + * @return boolean|null + */ + public function chroot($fakeRoot) { + if (!$fakeRoot == '') { + if ($fakeRoot[0] !== '/') { + $fakeRoot = '/' . $fakeRoot; + } + } + $this->fakeRoot = $fakeRoot; + } + + /** + * get the fake root + * + * @return string + */ + public function getRoot() { + return $this->fakeRoot; + } + + /** + * get path relative to the root of the view + * + * @param string $path + * @return string + */ + public function getRelativePath($path) { + $this->assertPathLength($path); + if ($this->fakeRoot == '') { + return $path; + } + + if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) { + return '/'; + } + + // missing slashes can cause wrong matches! + $root = rtrim($this->fakeRoot, '/') . '/'; + + if (strpos($path, $root) !== 0) { + return null; + } else { + $path = substr($path, strlen($this->fakeRoot)); + if (strlen($path) === 0) { + return '/'; + } else { + return $path; + } + } + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and does not take the chroot into account ) + * + * @param string $path + * @return string + */ + public function getMountPoint($path) { + return Filesystem::getMountPoint($this->getAbsolutePath($path)); + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and does not take the chroot into account ) + * + * @param string $path + * @return \OCP\Files\Mount\IMountPoint + */ + public function getMount($path) { + return Filesystem::getMountManager()->find($this->getAbsolutePath($path)); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array an array consisting of the storage and the internal path + */ + public function resolvePath($path) { + $a = $this->getAbsolutePath($path); + $p = Filesystem::normalizePath($a); + return Filesystem::resolvePath($p); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + [$storage, $internalPath] = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFile($internalPath); + } else { + return null; + } + } + + /** + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + [$storage, $internalPath] = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFolder($internalPath); + } else { + return null; + } + } + + /** + * the following functions operate with arguments and return values identical + * to those of their PHP built-in equivalents. Mostly they are merely wrappers + * for \OC\Files\Storage\Storage via basicOperation(). + */ + public function mkdir($path) { + return $this->basicOperation('mkdir', $path, ['create', 'write']); + } + + /** + * remove mount point + * + * @param \OC\Files\Mount\MoveableMount $mount + * @param string $path relative to data/ + * @return boolean + */ + protected function removeMount($mount, $path) { + if ($mount instanceof MoveableMount) { + // cut of /user/files to get the relative path to data/user/files + $pathParts = explode('/', $path, 4); + $relPath = '/' . $pathParts[3]; + $this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true); + \OC_Hook::emit( + Filesystem::CLASSNAME, "umount", + [Filesystem::signal_param_path => $relPath] + ); + $this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true); + $result = $mount->removeMount(); + $this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true); + if ($result) { + \OC_Hook::emit( + Filesystem::CLASSNAME, "post_umount", + [Filesystem::signal_param_path => $relPath] + ); + } + $this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true); + return $result; + } else { + // do not allow deleting the storage's root / the mount point + // because for some storages it might delete the whole contents + // but isn't supposed to work that way + return false; + } + } + + public function disableCacheUpdate() { + $this->updaterEnabled = false; + } + + public function enableCacheUpdate() { + $this->updaterEnabled = true; + } + + protected function writeUpdate(Storage $storage, $internalPath, $time = null) { + if ($this->updaterEnabled) { + if (is_null($time)) { + $time = time(); + } + $storage->getUpdater()->update($internalPath, $time); + } + } + + protected function removeUpdate(Storage $storage, $internalPath) { + if ($this->updaterEnabled) { + $storage->getUpdater()->remove($internalPath); + } + } + + protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) { + if ($this->updaterEnabled) { + $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } + + /** + * @param string $path + * @return bool|mixed + */ + public function rmdir($path) { + $absolutePath = $this->getAbsolutePath($path); + $mount = Filesystem::getMountManager()->find($absolutePath); + if ($mount->getInternalPath($absolutePath) === '') { + return $this->removeMount($mount, $absolutePath); + } + if ($this->is_dir($path)) { + $result = $this->basicOperation('rmdir', $path, ['delete']); + } else { + $result = false; + } + + if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($absolutePath); + $storage->getUpdater()->remove($internalPath); + } + return $result; + } + + /** + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->basicOperation('opendir', $path, ['read']); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function is_dir($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('is_dir', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function is_file($path) { + if ($path == '/') { + return false; + } + return $this->basicOperation('is_file', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function stat($path) { + return $this->basicOperation('stat', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filetype($path) { + return $this->basicOperation('filetype', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filesize($path) { + return $this->basicOperation('filesize', $path); + } + + /** + * @param string $path + * @return bool|mixed + * @throws \OCP\Files\InvalidPathException + */ + public function readfile($path) { + $this->assertPathLength($path); + @ob_end_clean(); + $handle = $this->fopen($path, 'rb'); + if ($handle) { + $chunkSize = 524288; // 512 kB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + flush(); + } + fclose($handle); + return $this->filesize($path); + } + return false; + } + + /** + * @param string $path + * @param int $from + * @param int $to + * @return bool|mixed + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\UnseekableException + */ + public function readfilePart($path, $from, $to) { + $this->assertPathLength($path); + @ob_end_clean(); + $handle = $this->fopen($path, 'rb'); + if ($handle) { + $chunkSize = 524288; // 512 kB chunks + $startReading = true; + + if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) { + // forward file handle via chunked fread because fseek seem to have failed + + $end = $from + 1; + while (!feof($handle) && ftell($handle) < $end) { + $len = $from - ftell($handle); + if ($len > $chunkSize) { + $len = $chunkSize; + } + $result = fread($handle, $len); + + if ($result === false) { + $startReading = false; + break; + } + } + } + + if ($startReading) { + $end = $to + 1; + while (!feof($handle) && ftell($handle) < $end) { + $len = $end - ftell($handle); + if ($len > $chunkSize) { + $len = $chunkSize; + } + echo fread($handle, $len); + flush(); + } + return ftell($handle) - $from; + } + + throw new \OCP\Files\UnseekableException('fseek error'); + } + return false; + } + + /** + * @param string $path + * @return mixed + */ + public function isCreatable($path) { + return $this->basicOperation('isCreatable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isReadable($path) { + return $this->basicOperation('isReadable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isUpdatable($path) { + return $this->basicOperation('isUpdatable', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function isDeletable($path) { + $absolutePath = $this->getAbsolutePath($path); + $mount = Filesystem::getMountManager()->find($absolutePath); + if ($mount->getInternalPath($absolutePath) === '') { + return $mount instanceof MoveableMount; + } + return $this->basicOperation('isDeletable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isSharable($path) { + return $this->basicOperation('isSharable', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function file_exists($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('file_exists', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filemtime($path) { + return $this->basicOperation('filemtime', $path); + } + + /** + * @param string $path + * @param int|string $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + if (!is_null($mtime) and !is_numeric($mtime)) { + $mtime = strtotime($mtime); + } + + $hooks = ['touch']; + + if (!$this->file_exists($path)) { + $hooks[] = 'create'; + $hooks[] = 'write'; + } + try { + $result = $this->basicOperation('touch', $path, $hooks, $mtime); + } catch (\Exception $e) { + $this->logger->logException($e, ['level' => ILogger::INFO, 'message' => 'Error while setting modified time']); + $result = false; + } + if (!$result) { + // If create file fails because of permissions on external storage like SMB folders, + // check file exists and return false if not. + if (!$this->file_exists($path)) { + return false; + } + if (is_null($mtime)) { + $mtime = time(); + } + //if native touch fails, we emulate it by changing the mtime in the cache + $this->putFileInfo($path, ['mtime' => floor($mtime)]); + } + return true; + } + + /** + * @param string $path + * @return mixed + * @throws LockedException + */ + public function file_get_contents($path) { + return $this->basicOperation('file_get_contents', $path, ['read']); + } + + /** + * @param bool $exists + * @param string $path + * @param bool $run + */ + protected function emit_file_hooks_pre($exists, $path, &$run) { + if (!$exists) { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [ + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + ]); + } else { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [ + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + ]); + } + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [ + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + ]); + } + + /** + * @param bool $exists + * @param string $path + */ + protected function emit_file_hooks_post($exists, $path) { + if (!$exists) { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [ + Filesystem::signal_param_path => $this->getHookPath($path), + ]); + } else { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [ + Filesystem::signal_param_path => $this->getHookPath($path), + ]); + } + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [ + Filesystem::signal_param_path => $this->getHookPath($path), + ]); + } + + /** + * @param string $path + * @param string|resource $data + * @return bool|mixed + * @throws LockedException + */ + public function file_put_contents($path, $data) { + if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + + $this->lockFile($path, ILockingProvider::LOCK_SHARED); + + $exists = $this->file_exists($path); + $run = true; + if ($this->shouldEmitHooks($path)) { + $this->emit_file_hooks_pre($exists, $path, $run); + } + if (!$run) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + return false; + } + + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + + /** @var \OC\Files\Storage\Storage $storage */ + [$storage, $internalPath] = $this->resolvePath($path); + $target = $storage->fopen($internalPath, 'w'); + if ($target) { + [, $result] = \OC_Helper::streamCopy($data, $target); + fclose($target); + fclose($data); + + $this->writeUpdate($storage, $internalPath); + + $this->changeLock($path, ILockingProvider::LOCK_SHARED); + + if ($this->shouldEmitHooks($path) && $result !== false) { + $this->emit_file_hooks_post($exists, $path); + } + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + return $result; + } else { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + return false; + } + } else { + return false; + } + } else { + $hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write']; + return $this->basicOperation('file_put_contents', $path, $hooks, $data); + } + } + + /** + * @param string $path + * @return bool|mixed + */ + public function unlink($path) { + if ($path === '' || $path === '/') { + // do not allow deleting the root + return false; + } + $postFix = (substr($path, -1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + $mount = Filesystem::getMountManager()->find($absolutePath . $postFix); + if ($mount and $mount->getInternalPath($absolutePath) === '') { + return $this->removeMount($mount, $absolutePath); + } + if ($this->is_dir($path)) { + $result = $this->basicOperation('rmdir', $path, ['delete']); + } else { + $result = $this->basicOperation('unlink', $path, ['delete']); + } + if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($absolutePath); + $storage->getUpdater()->remove($internalPath); + return true; + } else { + return $result; + } + } + + /** + * @param string $directory + * @return bool|mixed + */ + public function deleteAll($directory) { + return $this->rmdir($directory); + } + + /** + * Rename/move a file or folder from the source path to target path. + * + * @param string $path1 source path + * @param string $path2 target path + * + * @return bool|mixed + * @throws LockedException + */ + public function rename($path1, $path2) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + $result = false; + if ( + Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + $exists = $this->file_exists($path2); + + if ($path1 == null or $path2 == null) { + return false; + } + + $this->lockFile($path1, ILockingProvider::LOCK_SHARED, true); + try { + $this->lockFile($path2, ILockingProvider::LOCK_SHARED, true); + + $run = true; + if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + $this->emit_file_hooks_pre($exists, $path2, $run); + } elseif ($this->shouldEmitHooks($path1)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_rename, + [ + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ] + ); + } + if ($run) { + $this->verifyPath(dirname($path2), basename($path2)); + + $manager = Filesystem::getMountManager(); + $mount1 = $this->getMount($path1); + $mount2 = $this->getMount($path2); + $storage1 = $mount1->getStorage(); + $storage2 = $mount2->getStorage(); + $internalPath1 = $mount1->getInternalPath($absolutePath1); + $internalPath2 = $mount2->getInternalPath($absolutePath2); + + $this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true); + try { + $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true); + + if ($internalPath1 === '') { + if ($mount1 instanceof MoveableMount) { + $sourceParentMount = $this->getMount(dirname($path1)); + if ($sourceParentMount === $mount2 && $this->targetIsNotShared($storage2, $internalPath2)) { + /** + * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1 + */ + $sourceMountPoint = $mount1->getMountPoint(); + $result = $mount1->moveMount($absolutePath2); + $manager->moveMount($sourceMountPoint, $mount1->getMountPoint()); + } else { + $result = false; + } + } else { + $result = false; + } + // moving a file/folder within the same mount point + } elseif ($storage1 === $storage2) { + if ($storage1) { + $result = $storage1->rename($internalPath1, $internalPath2); + } else { + $result = false; + } + // moving a file/folder between storages (from $storage1 to $storage2) + } else { + $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); + } + + if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + $this->writeUpdate($storage2, $internalPath2); + } elseif ($result) { + if ($internalPath1 !== '') { // don't do a cache update for moved mounts + $this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2); + } + } + } catch (\Exception $e) { + throw $e; + } finally { + $this->changeLock($path1, ILockingProvider::LOCK_SHARED, true); + $this->changeLock($path2, ILockingProvider::LOCK_SHARED, true); + } + + if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + if ($this->shouldEmitHooks()) { + $this->emit_file_hooks_post($exists, $path2); + } + } elseif ($result) { + if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_rename, + [ + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ] + ); + } + } + } + } catch (\Exception $e) { + throw $e; + } finally { + $this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true); + $this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true); + } + } + return $result; + } + + /** + * Copy a file/folder from the source path to target path + * + * @param string $path1 source path + * @param string $path2 target path + * @param bool $preserveMtime whether to preserve mtime on the copy + * + * @return bool|mixed + */ + public function copy($path1, $path2, $preserveMtime = false) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + $result = false; + if ( + Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + + $this->lockFile($path2, ILockingProvider::LOCK_SHARED); + $this->lockFile($path1, ILockingProvider::LOCK_SHARED); + $lockTypePath1 = ILockingProvider::LOCK_SHARED; + $lockTypePath2 = ILockingProvider::LOCK_SHARED; + + try { + $exists = $this->file_exists($path2); + if ($this->shouldEmitHooks()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_copy, + [ + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ] + ); + $this->emit_file_hooks_pre($exists, $path2, $run); + } + if ($run) { + $mount1 = $this->getMount($path1); + $mount2 = $this->getMount($path2); + $storage1 = $mount1->getStorage(); + $internalPath1 = $mount1->getInternalPath($absolutePath1); + $storage2 = $mount2->getStorage(); + $internalPath2 = $mount2->getInternalPath($absolutePath2); + + $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE); + $lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE; + + if ($mount1->getMountPoint() == $mount2->getMountPoint()) { + if ($storage1) { + $result = $storage1->copy($internalPath1, $internalPath2); + } else { + $result = false; + } + } else { + $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2); + } + + $this->writeUpdate($storage2, $internalPath2); + + $this->changeLock($path2, ILockingProvider::LOCK_SHARED); + $lockTypePath2 = ILockingProvider::LOCK_SHARED; + + if ($this->shouldEmitHooks() && $result !== false) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_copy, + [ + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ] + ); + $this->emit_file_hooks_post($exists, $path2); + } + } + } catch (\Exception $e) { + $this->unlockFile($path2, $lockTypePath2); + $this->unlockFile($path1, $lockTypePath1); + throw $e; + } + + $this->unlockFile($path2, $lockTypePath2); + $this->unlockFile($path1, $lockTypePath1); + } + return $result; + } + + /** + * @param string $path + * @param string $mode 'r' or 'w' + * @return resource + * @throws LockedException + */ + public function fopen($path, $mode) { + $mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support + $hooks = []; + switch ($mode) { + case 'r': + $hooks[] = 'read'; + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + $hooks[] = 'read'; + $hooks[] = 'write'; + break; + case 'w': + case 'x': + case 'a': + $hooks[] = 'write'; + break; + default: + \OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR); + } + + if ($mode !== 'r' && $mode !== 'w') { + \OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends'); + } + + return $this->basicOperation('fopen', $path, $hooks, $mode); + } + + /** + * @param string $path + * @return bool|string + * @throws \OCP\Files\InvalidPathException + */ + public function toTmpFile($path) { + $this->assertPathLength($path); + if (Filesystem::isValidPath($path)) { + $source = $this->fopen($path, 'r'); + if ($source) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); + file_put_contents($tmpFile, $source); + return $tmpFile; + } else { + return false; + } + } else { + return false; + } + } + + /** + * @param string $tmpFile + * @param string $path + * @return bool|mixed + * @throws \OCP\Files\InvalidPathException + */ + public function fromTmpFile($tmpFile, $path) { + $this->assertPathLength($path); + if (Filesystem::isValidPath($path)) { + + // Get directory that the file is going into + $filePath = dirname($path); + + // Create the directories if any + if (!$this->file_exists($filePath)) { + $result = $this->createParentDirectories($filePath); + if ($result === false) { + return false; + } + } + + $source = fopen($tmpFile, 'r'); + if ($source) { + $result = $this->file_put_contents($path, $source); + // $this->file_put_contents() might have already closed + // the resource, so we check it, before trying to close it + // to avoid messages in the error log. + if (is_resource($source)) { + fclose($source); + } + unlink($tmpFile); + return $result; + } else { + return false; + } + } else { + return false; + } + } + + + /** + * @param string $path + * @return mixed + * @throws \OCP\Files\InvalidPathException + */ + public function getMimeType($path) { + $this->assertPathLength($path); + return $this->basicOperation('getMimeType', $path); + } + + /** + * @param string $type + * @param string $path + * @param bool $raw + * @return bool|null|string + */ + public function hash($type, $path, $raw = false) { + $postFix = (substr($path, -1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + if ($this->shouldEmitHooks($path)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_read, + [Filesystem::signal_param_path => $this->getHookPath($path)] + ); + } + [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); + if ($storage) { + return $storage->hash($type, $internalPath, $raw); + } + } + return null; + } + + /** + * @param string $path + * @return mixed + * @throws \OCP\Files\InvalidPathException + */ + public function free_space($path = '/') { + $this->assertPathLength($path); + $result = $this->basicOperation('free_space', $path); + if ($result === null) { + throw new InvalidPathException(); + } + return $result; + } + + /** + * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage + * + * @param string $operation + * @param string $path + * @param array $hooks (optional) + * @param mixed $extraParam (optional) + * @return mixed + * @throws LockedException + * + * This method takes requests for basic filesystem functions (e.g. reading & writing + * files), processes hooks and proxies, sanitises paths, and finally passes them on to + * \OC\Files\Storage\Storage for delegation to a storage backend for execution + */ + private function basicOperation($operation, $path, $hooks = [], $extraParam = null) { + $postFix = (substr($path, -1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + + if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { + // always a shared lock during pre-hooks so the hook can read the file + $this->lockFile($path, ILockingProvider::LOCK_SHARED); + } + + $run = $this->runHooks($hooks, $path); + /** @var \OC\Files\Storage\Storage $storage */ + [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); + if ($run and $storage) { + if (in_array('write', $hooks) || in_array('delete', $hooks)) { + try { + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + } catch (LockedException $e) { + // release the shared lock we acquired before quiting + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + throw $e; + } + } + try { + if (!is_null($extraParam)) { + $result = $storage->$operation($internalPath, $extraParam); + } else { + $result = $storage->$operation($internalPath); + } + } catch (\Exception $e) { + if (in_array('write', $hooks) || in_array('delete', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + } elseif (in_array('read', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + throw $e; + } + + if ($result && in_array('delete', $hooks) and $result) { + $this->removeUpdate($storage, $internalPath); + } + if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') { + $this->writeUpdate($storage, $internalPath); + } + if ($result && in_array('touch', $hooks)) { + $this->writeUpdate($storage, $internalPath, $extraParam); + } + + if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) { + $this->changeLock($path, ILockingProvider::LOCK_SHARED); + } + + $unlockLater = false; + if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) { + $unlockLater = true; + // make sure our unlocking callback will still be called if connection is aborted + ignore_user_abort(true); + $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) { + if (in_array('write', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + } elseif (in_array('read', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + }); + } + + if ($this->shouldEmitHooks($path) && $result !== false) { + if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open + $this->runHooks($hooks, $path, true); + } + } + + if (!$unlockLater + && (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) + ) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + return $result; + } else { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + } + return null; + } + + /** + * get the path relative to the default root for hook usage + * + * @param string $path + * @return string + */ + private function getHookPath($path) { + if (!Filesystem::getView()) { + return $path; + } + return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path)); + } + + private function shouldEmitHooks($path = '') { + if ($path && Cache\Scanner::isPartialFile($path)) { + return false; + } + if (!Filesystem::$loaded) { + return false; + } + $defaultRoot = Filesystem::getRoot(); + if ($defaultRoot === null) { + return false; + } + if ($this->fakeRoot === $defaultRoot) { + return true; + } + $fullPath = $this->getAbsolutePath($path); + + if ($fullPath === $defaultRoot) { + return true; + } + + return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); + } + + /** + * @param string[] $hooks + * @param string $path + * @param bool $post + * @return bool + */ + private function runHooks($hooks, $path, $post = false) { + $relativePath = $path; + $path = $this->getHookPath($path); + $prefix = $post ? 'post_' : ''; + $run = true; + if ($this->shouldEmitHooks($relativePath)) { + foreach ($hooks as $hook) { + if ($hook != 'read') { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + [ + Filesystem::signal_param_run => &$run, + Filesystem::signal_param_path => $path + ] + ); + } elseif (!$post) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + [ + Filesystem::signal_param_path => $path + ] + ); + } + } + } + return $run; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->basicOperation('hasUpdated', $path, [], $time); + } + + /** + * @param string $ownerId + * @return \OC\User\User + */ + private function getUserObjectForOwner($ownerId) { + $owner = $this->userManager->get($ownerId); + if ($owner instanceof IUser) { + return $owner; + } else { + return new User($ownerId, null, \OC::$server->getEventDispatcher()); + } + } + + /** + * Get file info from cache + * + * If the file is not in cached it will be scanned + * If the file has changed on storage the cache will be updated + * + * @param \OC\Files\Storage\Storage $storage + * @param string $internalPath + * @param string $relativePath + * @return ICacheEntry|bool + */ + private function getCacheEntry($storage, $internalPath, $relativePath) { + $cache = $storage->getCache($internalPath); + $data = $cache->get($internalPath); + $watcher = $storage->getWatcher($internalPath); + + try { + // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data + if (!$data || $data['size'] === -1) { + if (!$storage->file_exists($internalPath)) { + return false; + } + // don't need to get a lock here since the scanner does it's own locking + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + $data = $cache->get($internalPath); + } elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) { + $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); + $watcher->update($internalPath, $data); + $storage->getPropagator()->propagateChange($internalPath, time()); + $data = $cache->get($internalPath); + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + } + } catch (LockedException $e) { + // if the file is locked we just use the old cache info + } + + return $data; + } + + /** + * get the filesystem info + * + * @param string $path + * @param boolean|string $includeMountPoints true to add mountpoint sizes, + * 'ext' to add only ext storage mount point sizes. Defaults to true. + * defaults to true + * @return \OC\Files\FileInfo|false False if file does not exist + */ + public function getFileInfo($path, $includeMountPoints = true) { + $this->assertPathLength($path); + if (!Filesystem::isValidPath($path)) { + return false; + } + if (Cache\Scanner::isPartialFile($path)) { + return $this->getPartFileInfo($path); + } + $relativePath = $path; + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + + $mount = Filesystem::getMountManager()->find($path); + if (!$mount) { + \OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path); + return false; + } + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + if ($storage) { + $data = $this->getCacheEntry($storage, $internalPath, $relativePath); + + if (!$data instanceof ICacheEntry) { + return false; + } + + if ($mount instanceof MoveableMount && $internalPath === '') { + $data['permissions'] |= \OCP\Constants::PERMISSION_DELETE; + } + $ownerId = $storage->getOwner($internalPath); + $owner = null; + if ($ownerId !== null && $ownerId !== false) { + // ownerId might be null if files are accessed with an access token without file system access + $owner = $this->getUserObjectForOwner($ownerId); + } + $info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner); + + if ($data and isset($data['fileid'])) { + if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mount points to the folder + $extOnly = ($includeMountPoints === 'ext'); + $mounts = Filesystem::getMountManager()->findIn($path); + $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) { + $subStorage = $mount->getStorage(); + return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage); + })); + } + } + + return $info; + } else { + \OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint()); + } + + return false; + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return FileInfo[] + */ + public function getDirectoryContent($directory, $mimetype_filter = '') { + $this->assertPathLength($directory); + if (!Filesystem::isValidPath($directory)) { + return []; + } + $path = $this->getAbsolutePath($directory); + $path = Filesystem::normalizePath($path); + $mount = $this->getMount($directory); + if (!$mount) { + return []; + } + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $user = \OC_User::getUser(); + + $data = $this->getCacheEntry($storage, $internalPath, $directory); + + if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { + return []; + } + + $folderId = $data['fileid']; + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); + + $fileNames = array_map(function (ICacheEntry $content) { + return $content->getName(); + }, $contents); + /** + * @var \OC\Files\FileInfo[] $fileInfos + */ + $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { + $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); + return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); + }, $contents); + $files = array_combine($fileNames, $fileInfos); + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = Filesystem::getMountManager()->findIn($path); + $dirLength = strlen($path); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + $rootEntry = $subCache->get(''); + if (!$rootEntry) { + $subScanner = $subStorage->getScanner(''); + try { + $subScanner->scanFile(''); + } catch (\OCP\Files\StorageNotAvailableException $e) { + continue; + } catch (\OCP\Files\StorageInvalidException $e) { + continue; + } catch (\Exception $e) { + // sometimes when the storage is not available it can be any exception + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', + 'level' => ILogger::ERROR, + 'app' => 'lib', + ]); + continue; + } + $rootEntry = $subCache->get(''); + } + + if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry->getName() === $entryName) { + $entry->addSubEntry($rootEntry, $mountPoint); + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $permissions = $rootEntry['permissions']; + // do not allow renaming/deleting the mount point if they are not shared files/folders + // for shared files/folders we use the permissions given by the owner + if ($mount instanceof MoveableMount) { + $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + } + + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ + + // if sharing was disabled for the user we remove the share permissions + if (\OCP\Util::isSharingDisabledForUser()) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + + $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); + $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + } + } + } + } + + if ($mimetype_filter) { + $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { + if (strpos($mimetype_filter, '/')) { + return $file->getMimetype() === $mimetype_filter; + } else { + return $file->getMimePart() === $mimetype_filter; + } + }); + } + + return array_values($files); + } else { + return []; + } + } + + /** + * change file metadata + * + * @param string $path + * @param array|\OCP\Files\FileInfo $data + * @return int + * + * returns the fileid of the updated file + */ + public function putFileInfo($path, $data) { + $this->assertPathLength($path); + if ($data instanceof FileInfo) { + $data = $data->getData(); + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + [$storage, $internalPath] = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($path); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } else { + return -1; + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return FileInfo[] + */ + public function search($query) { + return $this->searchCommon('search', ['%' . $query . '%']); + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return FileInfo[] + */ + public function searchRaw($query) { + return $this->searchCommon('search', [$query]); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return FileInfo[] + */ + public function searchByMime($mimetype) { + return $this->searchCommon('searchByMime', [$mimetype]); + } + + /** + * search for files by tag + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return FileInfo[] + */ + public function searchByTag($tag, $userId) { + return $this->searchCommon('searchByTag', [$tag, $userId]); + } + + /** + * @param string $method cache method + * @param array $args + * @return FileInfo[] + */ + private function searchCommon($method, $args) { + $files = []; + $rootLength = strlen($this->fakeRoot); + + $mount = $this->getMount(''); + $mountPoint = $mount->getMountPoint(); + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $results = call_user_func_array([$cache, $method], $args); + foreach ($results as $result) { + if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') { + $internalPath = $result['path']; + $path = $mountPoint . $result['path']; + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); + } + } + + $mounts = Filesystem::getMountManager()->findIn($this->fakeRoot); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = call_user_func_array([$cache, $method], $args); + if ($results) { + foreach ($results as $result) { + $internalPath = $result['path']; + $result['path'] = rtrim($relativeMountPoint . $result['path'], '/'); + $path = rtrim($mountPoint . $internalPath, '/'); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); + } + } + } + } + } + return $files; + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string the user id of the owner + * @throws NotFoundException + */ + public function getOwner($path) { + $info = $this->getFileInfo($path); + if (!$info) { + throw new NotFoundException($path . ' not found while trying to get owner'); + } + + if ($info->getOwner() === null) { + throw new NotFoundException($path . ' has no owner'); + } + + return $info->getOwner()->getUID(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + /** + * @var Storage\Storage $storage + * @var string $internalPath + */ + [$storage, $internalPath] = $this->resolvePath($path); + if ($storage) { + return $storage->getETag($internalPath); + } else { + return null; + } + } + + /** + * Get the path of a file by id, relative to the view + * + * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @param int|null $storageId + * @return string + * @throws NotFoundException + */ + public function getPath($id, int $storageId = null) { + $id = (int)$id; + $manager = Filesystem::getMountManager(); + $mounts = $manager->findIn($this->fakeRoot); + $mounts[] = $manager->find($this->fakeRoot); + // reverse the array so we start with the storage this view is in + // which is the most likely to contain the file we're looking for + $mounts = array_reverse($mounts); + + // put non shared mounts in front of the shared mount + // this prevent unneeded recursion into shares + usort($mounts, function (IMountPoint $a, IMountPoint $b) { + return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1; + }); + + if (!is_null($storageId)) { + $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) { + return $mount->getNumericStorageId() === $storageId; + }); + } + + foreach ($mounts as $mount) { + /** + * @var \OC\Files\Mount\MountPoint $mount + */ + if ($mount->getStorage()) { + $cache = $mount->getStorage()->getCache(); + $internalPath = $cache->getPathById($id); + if (is_string($internalPath)) { + $fullPath = $mount->getMountPoint() . $internalPath; + if (!is_null($path = $this->getRelativePath($fullPath))) { + return $path; + } + } + } + } + throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id)); + } + + /** + * @param string $path + * @throws InvalidPathException + */ + private function assertPathLength($path) { + $maxLen = min(PHP_MAXPATHLEN, 4000); + // Check for the string length - performed using isset() instead of strlen() + // because isset() is about 5x-40x faster. + if (isset($path[$maxLen])) { + $pathLen = strlen($path); + throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path"); + } + } + + /** + * check if it is allowed to move a mount point to a given target. + * It is not allowed to move a mount point into a different mount point or + * into an already shared folder + * + * @param IStorage $targetStorage + * @param string $targetInternalPath + * @return boolean + */ + private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) { + + // note: cannot use the view because the target is already locked + $fileId = (int)$targetStorage->getCache()->getId($targetInternalPath); + if ($fileId === -1) { + // target might not exist, need to check parent instead + $fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath)); + } + + // check if any of the parents were shared by the current owner (include collections) + $shares = \OCP\Share::getItemShared( + 'folder', + $fileId, + \OCP\Share::FORMAT_NONE, + null, + true + ); + + if (count($shares) > 0) { + \OCP\Util::writeLog('files', + 'It is not allowed to move one mount point into a shared folder', + ILogger::DEBUG); + return false; + } + + return true; + } + + /** + * Get a fileinfo object for files that are ignored in the cache (part files) + * + * @param string $path + * @return \OCP\Files\FileInfo + */ + private function getPartFileInfo($path) { + $mount = $this->getMount($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($this->getAbsolutePath($path)); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + return new FileInfo( + $this->getAbsolutePath($path), + $storage, + $internalPath, + [ + 'fileid' => null, + 'mimetype' => $storage->getMimeType($internalPath), + 'name' => basename($path), + 'etag' => null, + 'size' => $storage->filesize($internalPath), + 'mtime' => $storage->filemtime($internalPath), + 'encrypted' => false, + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], + $mount, + $owner + ); + } + + /** + * @param string $path + * @param string $fileName + * @throws InvalidPathException + */ + public function verifyPath($path, $fileName) { + try { + /** @type \OCP\Files\Storage $storage */ + [$storage, $internalPath] = $this->resolvePath($path); + $storage->verifyPath($internalPath, $fileName); + } catch (ReservedWordException $ex) { + $l = \OC::$server->getL10N('lib'); + throw new InvalidPathException($l->t('File name is a reserved word')); + } catch (InvalidCharacterInPathException $ex) { + $l = \OC::$server->getL10N('lib'); + throw new InvalidPathException($l->t('File name contains at least one invalid character')); + } catch (FileNameTooLongException $ex) { + $l = \OC::$server->getL10N('lib'); + throw new InvalidPathException($l->t('File name is too long')); + } catch (InvalidDirectoryException $ex) { + $l = \OC::$server->getL10N('lib'); + throw new InvalidPathException($l->t('Dot files are not allowed')); + } catch (EmptyFileNameException $ex) { + $l = \OC::$server->getL10N('lib'); + throw new InvalidPathException($l->t('Empty filename is not allowed')); + } + } + + /** + * get all parent folders of $path + * + * @param string $path + * @return string[] + */ + private function getParents($path) { + $path = trim($path, '/'); + if (!$path) { + return []; + } + + $parts = explode('/', $path); + + // remove the single file + array_pop($parts); + $result = ['/']; + $resultPath = ''; + foreach ($parts as $part) { + if ($part) { + $resultPath .= '/' . $part; + $result[] = $resultPath; + } + } + return $result; + } + + /** + * Returns the mount point for which to lock + * + * @param string $absolutePath absolute path + * @param bool $useParentMount true to return parent mount instead of whatever + * is mounted directly on the given path, false otherwise + * @return \OC\Files\Mount\MountPoint mount point for which to apply locks + */ + private function getMountForLock($absolutePath, $useParentMount = false) { + $results = []; + $mount = Filesystem::getMountManager()->find($absolutePath); + if (!$mount) { + return $results; + } + + if ($useParentMount) { + // find out if something is mounted directly on the path + $internalPath = $mount->getInternalPath($absolutePath); + if ($internalPath === '') { + // resolve the parent mount instead + $mount = Filesystem::getMountManager()->find(dirname($absolutePath)); + } + } + + return $mount; + } + + /** + * Lock the given path + * + * @param string $path the path of the file to lock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException if the path is already locked + */ + private function lockPath($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + try { + $storage = $mount->getStorage(); + if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->acquireLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } catch (LockedException $e) { + // rethrow with the a human-readable path + throw new LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e, + $e->getExistingLock() + ); + } + } + + return true; + } + + /** + * Change the lock type + * + * @param string $path the path of the file to lock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException if the path is already locked + */ + public function changeLock($path, $type, $lockMountPoint = false) { + $path = Filesystem::normalizePath($path); + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + try { + $storage = $mount->getStorage(); + if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->changeLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } catch (LockedException $e) { + try { + // rethrow with the a human-readable path + throw new LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e, + $e->getExistingLock() + ); + } catch (\InvalidArgumentException $ex) { + throw new LockedException( + $absolutePath, + $ex, + $e->getExistingLock() + ); + } + } + } + + return true; + } + + /** + * Unlock the given path + * + * @param string $path the path of the file to unlock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException + */ + private function unlockPath($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + $storage = $mount->getStorage(); + if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->releaseLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } + + return true; + } + + /** + * Lock a path and all its parents up to the root of the view + * + * @param string $path the path of the file to lock relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException + */ + public function lockFile($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $this->lockPath($path, $type, $lockMountPoint); + + $parents = $this->getParents($path); + foreach ($parents as $parent) { + $this->lockPath($parent, ILockingProvider::LOCK_SHARED); + } + + return true; + } + + /** + * Unlock a path and all its parents up to the root of the view + * + * @param string $path the path of the file to lock relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException + */ + public function unlockFile($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $this->unlockPath($path, $type, $lockMountPoint); + + $parents = $this->getParents($path); + foreach ($parents as $parent) { + $this->unlockPath($parent, ILockingProvider::LOCK_SHARED); + } + + return true; + } + + /** + * Only lock files in data/user/files/ + * + * @param string $path Absolute path to the file/folder we try to (un)lock + * @return bool + */ + protected function shouldLockFile($path) { + $path = Filesystem::normalizePath($path); + + $pathSegments = explode('/', $path); + if (isset($pathSegments[2])) { + // E.g.: /username/files/path-to-file + return ($pathSegments[2] === 'files') && (count($pathSegments) > 3); + } + + return strpos($path, '/appdata_') !== 0; + } + + /** + * Shortens the given absolute path to be relative to + * "$user/files". + * + * @param string $absolutePath absolute path which is under "files" + * + * @return string path relative to "files" with trimmed slashes or null + * if the path was NOT relative to files + * + * @throws \InvalidArgumentException if the given path was not under "files" + * @since 8.1.0 + */ + public function getPathRelativeToFiles($absolutePath) { + $path = Filesystem::normalizePath($absolutePath); + $parts = explode('/', trim($path, '/'), 3); + // "$user", "files", "path/to/dir" + if (!isset($parts[1]) || $parts[1] !== 'files') { + $this->logger->error( + '$absolutePath must be relative to "files", value is "%s"', + [ + $absolutePath + ] + ); + throw new \InvalidArgumentException('$absolutePath must be relative to "files"'); + } + if (isset($parts[2])) { + return $parts[2]; + } + return ''; + } + + /** + * @param string $filename + * @return array + * @throws \OC\User\NoUserException + * @throws NotFoundException + */ + public function getUidAndFilename($filename) { + $info = $this->getFileInfo($filename); + if (!$info instanceof \OCP\Files\FileInfo) { + throw new NotFoundException($this->getAbsolutePath($filename) . ' not found'); + } + $uid = $info->getOwner()->getUID(); + if ($uid != \OCP\User::getUser()) { + Filesystem::initMountPoints($uid); + $ownerView = new View('/' . $uid . '/files'); + try { + $filename = $ownerView->getPath($info['fileid']); + } catch (NotFoundException $e) { + throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid); + } + } + return [$uid, $filename]; + } + + /** + * Creates parent non-existing folders + * + * @param string $filePath + * @return bool + */ + private function createParentDirectories($filePath) { + $directoryParts = explode('/', $filePath); + $directoryParts = array_filter($directoryParts); + foreach ($directoryParts as $key => $part) { + $currentPathElements = array_slice($directoryParts, 0, $key); + $currentPath = '/' . implode('/', $currentPathElements); + if ($this->is_file($currentPath)) { + return false; + } + if (!$this->file_exists($currentPath)) { + $this->mkdir($currentPath); + } + } + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/ForbiddenException.php b/docker/overlays/nextcloud/html/lib/private/ForbiddenException.php new file mode 100644 index 0000000..1ba38e3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/ForbiddenException.php @@ -0,0 +1,31 @@ + + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +/** + * Exception thrown whenever access to a resource has + * been forbidden or whenever a user isn't authenticated. + */ +class ForbiddenException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/FullTextSearchManager.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/FullTextSearchManager.php new file mode 100644 index 0000000..119c97f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/FullTextSearchManager.php @@ -0,0 +1,233 @@ + + * + * @author Maxence Lange + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch; + +use OCP\FullTextSearch\Exceptions\FullTextSearchAppNotAvailableException; +use OCP\FullTextSearch\IFullTextSearchManager; +use OCP\FullTextSearch\Model\IIndex; +use OCP\FullTextSearch\Model\ISearchResult; +use OCP\FullTextSearch\Service\IIndexService; +use OCP\FullTextSearch\Service\IProviderService; +use OCP\FullTextSearch\Service\ISearchService; + +/** + * Class FullTextSearchManager + * + * @package OC\FullTextSearch + */ +class FullTextSearchManager implements IFullTextSearchManager { + + + /** @var IProviderService */ + private $providerService; + + /** @var IIndexService */ + private $indexService; + + /** @var ISearchService */ + private $searchService; + + + /** + * @since 15.0.0 + * + * @param IProviderService $providerService + */ + public function registerProviderService(IProviderService $providerService) { + $this->providerService = $providerService; + } + + /** + * @since 15.0.0 + * + * @param IIndexService $indexService + */ + public function registerIndexService(IIndexService $indexService) { + $this->indexService = $indexService; + } + + /** + * @since 15.0.0 + * + * @param ISearchService $searchService + */ + public function registerSearchService(ISearchService $searchService) { + $this->searchService = $searchService; + } + + /** + * @since 16.0.0 + * + * @return bool + */ + public function isAvailable(): bool { + if ($this->indexService === null || + $this->providerService === null || + $this->searchService === null) { + return false; + } + + return true; + } + + + /** + * @return IProviderService + * @throws FullTextSearchAppNotAvailableException + */ + private function getProviderService(): IProviderService { + if ($this->providerService === null) { + throw new FullTextSearchAppNotAvailableException('No IProviderService registered'); + } + + return $this->providerService; + } + + + /** + * @return IIndexService + * @throws FullTextSearchAppNotAvailableException + */ + private function getIndexService(): IIndexService { + if ($this->indexService === null) { + throw new FullTextSearchAppNotAvailableException('No IIndexService registered'); + } + + return $this->indexService; + } + + + /** + * @return ISearchService + * @throws FullTextSearchAppNotAvailableException + */ + private function getSearchService(): ISearchService { + if ($this->searchService === null) { + throw new FullTextSearchAppNotAvailableException('No ISearchService registered'); + } + + return $this->searchService; + } + + + /** + * @throws FullTextSearchAppNotAvailableException + */ + public function addJavascriptAPI() { + $this->getProviderService()->addJavascriptAPI(); + } + + + /** + * @param string $providerId + * + * @return bool + * @throws FullTextSearchAppNotAvailableException + */ + public function isProviderIndexed(string $providerId): bool { + return $this->getProviderService()->isProviderIndexed($providerId); + } + + + /** + * @param string $providerId + * @param string $documentId + * @return IIndex + * @throws FullTextSearchAppNotAvailableException + */ + public function getIndex(string $providerId, string $documentId): IIndex { + return $this->getIndexService()->getIndex($providerId, $documentId); + } + + /** + * @param string $providerId + * @param string $documentId + * @param string $userId + * @param int $status + * + * @see IIndex for available value for $status. + * + * @return IIndex + * @throws FullTextSearchAppNotAvailableException + */ + public function createIndex(string $providerId, string $documentId, string $userId, int $status = 0): IIndex { + return $this->getIndexService()->createIndex($providerId, $documentId, $userId, $status); + } + + + /** + * @param string $providerId + * @param string $documentId + * @param int $status + * @param bool $reset + * + * @see IIndex for available value for $status. + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexStatus(string $providerId, string $documentId, int $status, bool $reset = false) { + $this->getIndexService()->updateIndexStatus($providerId, $documentId, $status, $reset); + } + + /** + * @param string $providerId + * @param array $documentIds + * @param int $status + * @param bool $reset + * + * @see IIndex for available value for $status. + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexesStatus(string $providerId, array $documentIds, int $status, bool $reset = false) { + $this->getIndexService()->updateIndexesStatus($providerId, $documentIds, $status, $reset); + } + + + /** + * @param IIndex[] $indexes + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexes(array $indexes) { + $this->getIndexService()->updateIndexes($indexes); + } + + + /** + * @param array $request + * @param string $userId + * + * @return ISearchResult[] + * @throws FullTextSearchAppNotAvailableException + */ + public function search(array $request, string $userId = ''): array { + $searchRequest = $this->getSearchService()->generateSearchRequest($request); + + return $this->getSearchService()->search($userId, $searchRequest); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/DocumentAccess.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/DocumentAccess.php new file mode 100644 index 0000000..d4c7a06 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/DocumentAccess.php @@ -0,0 +1,358 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch\Model; + +use JsonSerializable; +use OCP\FullTextSearch\Model\IDocumentAccess; + +/** + * Class IDocumentAccess + * + * This object is used as a data transfer object when + * + * - indexing a document, + * - generating a search request. + * + * During the index, it is used to define which users, groups, circles, ... + * have access to the IndexDocument + * + * During the search, it is internally use to define to which group, circles, ... + * a user that perform the search belongs to. + * + * @see IIndexDocument::setAccess + * + * @since 16.0.0 + * + * @package OC\FullTextSearch\Model + */ +final class DocumentAccess implements IDocumentAccess, JsonSerializable { + + + /** @var string */ + private $ownerId; + + /** @var string */ + private $viewerId = ''; + + /** @var array */ + private $users = []; + + /** @var array */ + private $groups = []; + + /** @var array */ + private $circles = []; + + /** @var array */ + private $links = []; + + + /** + * Owner of the document can be set at the init of the object. + * + * @since 16.0.0 + * + * IDocumentAccess constructor. + * + * @param string $ownerId + */ + public function __construct(string $ownerId = '') { + $this->setOwnerId($ownerId); + } + + + /** + * Set the Owner of the document. + * + * @since 16.0.0 + * + * @param string $ownerId + * + * @return IDocumentAccess + */ + public function setOwnerId(string $ownerId): IDocumentAccess { + $this->ownerId = $ownerId; + + return $this; + } + + /** + * Get the Owner of the document. + * + * @since 16.0.0 + * + * @return string + */ + public function getOwnerId(): string { + return $this->ownerId; + } + + + /** + * Set the viewer of the document. + * + * @since 16.0.0 + * + * @param string $viewerId + * + * @return IDocumentAccess + */ + public function setViewerId(string $viewerId): IDocumentAccess { + $this->viewerId = $viewerId; + + return $this; + } + + /** + * Get the viewer of the document. + * + * @since 16.0.0 + * + * @return string + */ + public function getViewerId(): string { + return $this->viewerId; + } + + + /** + * Set the list of users that have read access to the document. + * + * @since 16.0.0 + * + * @param array $users + * + * @return IDocumentAccess + */ + public function setUsers(array $users): IDocumentAccess { + $this->users = $users; + + return $this; + } + + /** + * Add an entry to the list of users that have read access to the document. + * + * @since 16.0.0 + * + * @param string $user + * + * @return IDocumentAccess + */ + public function addUser(string $user): IDocumentAccess { + $this->users[] = $user; + + return $this; + } + + /** + * Add multiple entries to the list of users that have read access to the + * document. + * + * @since 16.0.0 + * + * @param array $users + * + * @return IDocumentAccess + */ + public function addUsers($users): IDocumentAccess { + $this->users = array_merge($this->users, $users); + + return $this; + } + + /** + * Get the complete list of users that have read access to the document. + * + * @since 16.0.0 + * + * @return array + */ + public function getUsers(): array { + return $this->users; + } + + + /** + * Set the list of groups that have read access to the document. + * + * @since 16.0.0 + * + * @param array $groups + * + * @return IDocumentAccess + */ + public function setGroups(array $groups): IDocumentAccess { + $this->groups = $groups; + + return $this; + } + + /** + * Add an entry to the list of groups that have read access to the document. + * + * @since 16.0.0 + * + * @param string $group + * + * @return IDocumentAccess + */ + public function addGroup(string $group): IDocumentAccess { + $this->groups[] = $group; + + return $this; + } + + /** + * Add multiple entries to the list of groups that have read access to the + * document. + * + * @since 16.0.0 + * + * @param array $groups + * + * @return IDocumentAccess + */ + public function addGroups(array $groups) { + $this->groups = array_merge($this->groups, $groups); + + return $this; + } + + /** + * Get the complete list of groups that have read access to the document. + * + * @since 16.0.0 + * + * @return array + */ + public function getGroups(): array { + return $this->groups; + } + + + /** + * Set the list of circles that have read access to the document. + * + * @since 16.0.0 + * + * @param array $circles + * + * @return IDocumentAccess + */ + public function setCircles(array $circles): IDocumentAccess { + $this->circles = $circles; + + return $this; + } + + /** + * Add an entry to the list of circles that have read access to the document. + * + * @since 16.0.0 + * + * @param string $circle + * + * @return IDocumentAccess + */ + public function addCircle(string $circle): IDocumentAccess { + $this->circles[] = $circle; + + return $this; + } + + /** + * Add multiple entries to the list of groups that have read access to the + * document. + * + * @since 16.0.0 + * + * @param array $circles + * + * @return IDocumentAccess + */ + public function addCircles(array $circles): IDocumentAccess { + $this->circles = array_merge($this->circles, $circles); + + return $this; + } + + /** + * Get the complete list of circles that have read access to the document. + * + * @since 16.0.0 + * + * @return array + */ + public function getCircles(): array { + return $this->circles; + } + + + /** + * Set the list of links that have read access to the document. + * + * @since 16.0.0 + * + * @param array $links + * + * @return IDocumentAccess + */ + public function setLinks(array $links): IDocumentAccess { + $this->links = $links; + + return $this; + } + + /** + * Get the list of links that have read access to the document. + * + * @since 16.0.0 + * + * @return array + */ + public function getLinks(): array { + return $this->links; + } + + + /** + * @since 16.0.0 + * + * @return array + */ + public function jsonSerialize(): array { + return [ + 'ownerId' => $this->getOwnerId(), + 'viewerId' => $this->getViewerId(), + 'users' => $this->getUsers(), + 'groups' => $this->getGroups(), + 'circles' => $this->getCircles(), + 'links' => $this->getLinks() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/IndexDocument.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/IndexDocument.php new file mode 100644 index 0000000..252aa66 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/IndexDocument.php @@ -0,0 +1,991 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch\Model; + +use JsonSerializable; +use OCP\FullTextSearch\Model\IDocumentAccess; +use OCP\FullTextSearch\Model\IIndex; +use OCP\FullTextSearch\Model\IIndexDocument; + +/** + * Class IndexDocument + * + * This is one of the main class of the FullTextSearch, used as a data transfer + * object. An IndexDocument is created to manage documents around FullTextSearch, + * during an index and during a search. + * The uniqueness of an IndexDocument is made by the Id of the Content Provider + * and the Id of the original document within the Content Provider. + * + * We will call original document the source from which the IndexDocument is + * generated. As an example, an original document can be a file, a mail, ... + * + * @since 15.0.0 + * + * @package OC\FullTextSearch\Model + */ +class IndexDocument implements IIndexDocument, JsonSerializable { + + + /** @var string */ + protected $id = ''; + + /** @var string */ + protected $providerId = ''; + + /** @var DocumentAccess */ + protected $access; + + /** @var IIndex */ + protected $index; + + /** @var int */ + protected $modifiedTime = 0; + + /** @var string */ + protected $source = ''; + + /** @var array */ + protected $tags = []; + + /** @var array */ + protected $metaTags = []; + + /** @var array */ + protected $subTags = []; + + /** @var string */ + protected $title = ''; + + /** @var string */ + protected $content = ''; + + /** @var string */ + protected $hash = ''; + + /** @var array */ + protected $parts = []; + + /** @var string */ + protected $link = ''; + + /** @var array */ + protected $more = []; + + /** @var array */ + protected $excerpts = []; + + /** @var string */ + protected $score = ''; + + /** @var array */ + protected $info = []; + + /** @var int */ + protected $contentEncoded = 0; + + + /** + * IIndexDocument constructor. + * + * On creation, we assure the uniqueness of the object using the providerId + * and the Id of the original document. + * + * @since 15.0.0 + * + * @param string $providerId + * @param string $documentId + */ + public function __construct(string $providerId, string $documentId) { + $this->providerId = $providerId; + $this->id = $documentId; + } + + + /** + * Returns the Id of the original document. + * + * @since 15.0.0 + * + * @return string + */ + final public function getId(): string { + return $this->id; + } + + + /** + * Returns the Id of the provider. + * + * @since 15.0.0 + * + * @return string + */ + final public function getProviderId(): string { + return $this->providerId; + } + + + /** + * Set the Index related to the IIndexDocument. + * + * @see IIndex + * + * @since 15.0.0 + * + * @param IIndex $index + * + * @return IIndexDocument + */ + final public function setIndex(IIndex $index): IIndexDocument { + $this->index = $index; + + return $this; + } + + /** + * Get the Index. + * + * @since 15.0.0 + * + * @return IIndex + */ + final public function getIndex(): IIndex { + return $this->index; + } + + /** + * return if Index is defined. + * + * @since 16.0.0 + * + * @return bool + */ + final public function hasIndex(): bool { + return ($this->index !== null); + } + + + /** + * Set the modified time of the original document. + * + * @since 15.0.0 + * + * @param int $modifiedTime + * + * @return IIndexDocument + */ + final public function setModifiedTime(int $modifiedTime): IIndexDocument { + $this->modifiedTime = $modifiedTime; + + return $this; + } + + /** + * Get the modified time of the original document. + * + * @since 15.0.0 + * + * @return int + */ + final public function getModifiedTime(): int { + return $this->modifiedTime; + } + + /** + * Check if the original document of the IIndexDocument is older than $time. + * + * @since 15.0.0 + * + * @param int $time + * + * @return bool + */ + final public function isOlderThan(int $time): bool { + return ($this->modifiedTime < $time); + } + + + /** + * Set the read rights of the original document using a IDocumentAccess. + * + * @see IDocumentAccess + * + * @since 15.0.0 + * + * @param IDocumentAccess $access + * + * @return $this + */ + final public function setAccess(IDocumentAccess $access): IIndexDocument { + $this->access = $access; + + return $this; + } + + /** + * Get the IDocumentAccess related to the original document. + * + * @since 15.0.0 + * + * @return IDocumentAccess + */ + final public function getAccess(): IDocumentAccess { + return $this->access; + } + + + /** + * Add a tag to the list. + * + * @since 15.0.0 + * + * @param string $tag + * + * @return IIndexDocument + */ + final public function addTag(string $tag): IIndexDocument { + $this->tags[] = $tag; + + return $this; + } + + /** + * Set the list of tags assigned to the original document. + * + * @since 15.0.0 + * + * @param array $tags + * + * @return IIndexDocument + */ + final public function setTags(array $tags): IIndexDocument { + $this->tags = $tags; + + return $this; + } + + /** + * Get the list of tags assigned to the original document. + * + * @since 15.0.0 + * + * @return array + */ + final public function getTags(): array { + return $this->tags; + } + + + /** + * Add a meta tag to the list. + * + * @since 15.0.0 + * + * @param string $tag + * + * @return IIndexDocument + */ + final public function addMetaTag(string $tag): IIndexDocument { + $this->metaTags[] = $tag; + + return $this; + } + + /** + * Set the list of meta tags assigned to the original document. + * + * @since 15.0.0 + * + * @param array $tags + * + * @return IIndexDocument + */ + final public function setMetaTags(array $tags): IIndexDocument { + $this->metaTags = $tags; + + return $this; + } + + /** + * Get the list of meta tags assigned to the original document. + * + * @since 15.0.0 + * + * @return array + */ + final public function getMetaTags(): array { + return $this->metaTags; + } + + + /** + * Add a sub tag to the list. + * + * @since 15.0.0 + * + * @param string $sub + * @param string $tag + * + * @return IIndexDocument + */ + final public function addSubTag(string $sub, string $tag): IIndexDocument { + if (!array_key_exists($sub, $this->subTags)) { + $this->subTags[$sub] = []; + } + + $this->subTags[$sub][] = $tag; + + return $this; + } + + + /** + * Set the list of sub tags assigned to the original document. + * + * @since 15.0.0 + * + * @param array $tags + * + * @return IIndexDocument + */ + final public function setSubTags(array $tags): IIndexDocument { + $this->subTags = $tags; + + return $this; + } + + /** + * Get the list of sub tags assigned to the original document. + * If $formatted is true, the result will be formatted in a one + * dimensional array. + * + * @since 15.0.0 + * + * @param bool $formatted + * + * @return array + */ + final public function getSubTags(bool $formatted = false): array { + if ($formatted === false) { + return $this->subTags; + } + + $subTags = []; + $ak = array_keys($this->subTags); + foreach ($ak as $source) { + $tags = $this->subTags[$source]; + foreach ($tags as $tag) { + $subTags[] = $source . '_' . $tag; + } + } + + return $subTags; + } + + + /** + * Set the source of the original document. + * + * @since 15.0.0 + * + * @param string $source + * + * @return IIndexDocument + */ + final public function setSource(string $source): IIndexDocument { + $this->source = $source; + + return $this; + } + + /** + * Get the source of the original document. + * + * @since 15.0.0 + * + * @return string + */ + final public function getSource(): string { + return $this->source; + } + + + /** + * Set the title of the original document. + * + * @since 15.0.0 + * + * @param string $title + * + * @return IIndexDocument + */ + final public function setTitle(string $title): IIndexDocument { + $this->title = $title; + + return $this; + } + + /** + * Get the title of the original document. + * + * @since 15.0.0 + * + * @return string + */ + final public function getTitle(): string { + return $this->title; + } + + + /** + * Set the content of the document. + * $encoded can be NOT_ENCODED or ENCODED_BASE64 if the content is raw or + * encoded in base64. + * + * @since 15.0.0 + * + * @param string $content + * @param int $encoded + * + * @return IIndexDocument + */ + final public function setContent(string $content, int $encoded = 0): IIndexDocument { + $this->content = $content; + $this->contentEncoded = $encoded; + + return $this; + } + + /** + * Get the content of the original document. + * + * @since 15.0.0 + * + * @return string + */ + final public function getContent(): string { + return $this->content; + } + + /** + * Returns the type of the encoding on the content. + * + * @since 15.0.0 + * + * @return int + */ + final public function isContentEncoded(): int { + return $this->contentEncoded; + } + + /** + * Return the size of the content. + * + * @since 15.0.0 + * + * @return int + */ + final public function getContentSize(): int { + return strlen($this->getContent()); + } + + + /** + * Generate an hash, based on the content of the original document. + * + * @since 15.0.0 + * + * @return IIndexDocument + */ + final public function initHash(): IIndexDocument { + if ($this->getContent() === '' || is_null($this->getContent())) { + return $this; + } + + $this->hash = hash("md5", $this->getContent()); + + return $this; + } + + /** + * Set the hash of the original document. + * + * @since 15.0.0 + * + * @param string $hash + * + * @return IIndexDocument + */ + final public function setHash(string $hash): IIndexDocument { + $this->hash = $hash; + + return $this; + } + + /** + * Get the hash of the original document. + * + * @since 15.0.0 + * + * @return string + */ + final public function getHash(): string { + return $this->hash; + } + + + /** + * Add a part, identified by a string, and its content. + * + * It is strongly advised to use alphanumerical chars with no space in the + * $part string. + * + * @since 15.0.0 + * + * @param string $part + * @param string $content + * + * @return IIndexDocument + */ + final public function addPart(string $part, string $content): IIndexDocument { + $this->parts[$part] = $content; + + return $this; + } + + /** + * Set all parts and their content. + * + * @since 15.0.0 + * + * @param array $parts + * + * @return IIndexDocument + */ + final public function setParts(array $parts): IIndexDocument { + $this->parts = $parts; + + return $this; + } + + /** + * Get all parts of the IIndexDocument. + * + * @since 15.0.0 + * + * @return array + */ + final public function getParts(): array { + return $this->parts; + } + + + /** + * Add a link, usable by the frontend. + * + * @since 15.0.0 + * + * @param string $link + * + * @return IIndexDocument + */ + final public function setLink(string $link): IIndexDocument { + $this->link = $link; + + return $this; + } + + /** + * Get the link. + * + * @since 15.0.0 + * + * @return string + */ + final public function getLink(): string { + return $this->link; + } + + + /** + * Set more information that couldn't be set using other method. + * + * @since 15.0.0 + * + * @param array $more + * + * @return IIndexDocument + */ + final public function setMore(array $more): IIndexDocument { + $this->more = $more; + + return $this; + } + + /** + * Get more information. + * + * @since 15.0.0 + * + * @return array + */ + final public function getMore(): array { + return $this->more; + } + + + /** + * Add some excerpt of the content of the original document, usually based + * on the search request. + * + * @since 16.0.0 + * + * @param string $source + * @param string $excerpt + * + * @return IIndexDocument + */ + final public function addExcerpt(string $source, string $excerpt): IIndexDocument { + $this->excerpts[] = + [ + 'source' => $source, + 'excerpt' => $this->cleanExcerpt($excerpt) + ]; + + return $this; + } + + + /** + * Set all excerpts of the content of the original document. + * + * @since 16.0.0 + * + * @param array $excerpts + * + * @return IIndexDocument + */ + final public function setExcerpts(array $excerpts): IIndexDocument { + $new = []; + foreach ($excerpts as $entry) { + $new[] = [ + 'source' => $entry['source'], + 'excerpt' => $this->cleanExcerpt($entry['excerpt']) + ]; + } + + $this->excerpts = $new; + + return $this; + } + + /** + * Get all excerpts of the content of the original document. + * + * @since 15.0.0 + * + * @return array + */ + final public function getExcerpts(): array { + return $this->excerpts; + } + + /** + * Clean excerpt. + * + * @since 16.0.0 + * + * @param string $excerpt + * @return string + */ + final private function cleanExcerpt(string $excerpt): string { + $excerpt = str_replace("\\n", ' ', $excerpt); + $excerpt = str_replace("\\r", ' ', $excerpt); + $excerpt = str_replace("\\t", ' ', $excerpt); + $excerpt = str_replace("\n", ' ', $excerpt); + $excerpt = str_replace("\r", ' ', $excerpt); + $excerpt = str_replace("\t", ' ', $excerpt); + + return $excerpt; + } + + + /** + * Set the score to the result assigned to this document during a search + * request. + * + * @since 15.0.0 + * + * @param string $score + * + * @return IIndexDocument + */ + final public function setScore(string $score): IIndexDocument { + $this->score = $score; + + return $this; + } + + /** + * Get the score. + * + * @since 15.0.0 + * + * @return string + */ + final public function getScore(): string { + return $this->score; + } + + + /** + * Set some information about the original document that will be available + * to the front-end when displaying search result. (as string) + * Because this information will not be indexed, this method can also be + * used to manage some data while filling the IIndexDocument before its + * indexing. + * + * @since 15.0.0 + * + * @param string $info + * @param string $value + * + * @return IIndexDocument + */ + final public function setInfo(string $info, string $value): IIndexDocument { + $this->info[$info] = $value; + + return $this; + } + + /** + * Get an information about a document. (string) + * + * @since 15.0.0 + * + * @param string $info + * @param string $default + * + * @return string + */ + final public function getInfo(string $info, string $default = ''): string { + if (!key_exists($info, $this->info)) { + return $default; + } + + return $this->info[$info]; + } + + /** + * Set some information about the original document that will be available + * to the front-end when displaying search result. (as array) + * Because this information will not be indexed, this method can also be + * used to manage some data while filling the IIndexDocument before its + * indexing. + * + * @since 15.0.0 + * + * @param string $info + * @param array $value + * + * @return IIndexDocument + */ + final public function setInfoArray(string $info, array $value): IIndexDocument { + $this->info[$info] = $value; + + return $this; + } + + /** + * Get an information about a document. (array) + * + * @since 15.0.0 + * + * @param string $info + * @param array $default + * + * @return array + */ + final public function getInfoArray(string $info, array $default = []): array { + if (!key_exists($info, $this->info)) { + return $default; + } + + return $this->info[$info]; + } + + /** + * Set some information about the original document that will be available + * to the front-end when displaying search result. (as int) + * Because this information will not be indexed, this method can also be + * used to manage some data while filling the IIndexDocument before its + * indexing. + * + * @since 15.0.0 + * + * @param string $info + * @param int $value + * + * @return IIndexDocument + */ + final public function setInfoInt(string $info, int $value): IIndexDocument { + $this->info[$info] = $value; + + return $this; + } + + /** + * Get an information about a document. (int) + * + * @since 15.0.0 + * + * @param string $info + * @param int $default + * + * @return int + */ + final public function getInfoInt(string $info, int $default = 0): int { + if (!key_exists($info, $this->info)) { + return $default; + } + + return $this->info[$info]; + } + + /** + * Set some information about the original document that will be available + * to the front-end when displaying search result. (as bool) + * Because this information will not be indexed, this method can also be + * used to manage some data while filling the IIndexDocument before its + * indexing. + * + * @since 15.0.0 + * + * @param string $info + * @param bool $value + * + * @return IIndexDocument + */ + final public function setInfoBool(string $info, bool $value): IIndexDocument { + $this->info[$info] = $value; + + return $this; + } + + /** + * Get an information about a document. (bool) + * + * @since 15.0.0 + * + * @param string $info + * @param bool $default + * + * @return bool + */ + final public function getInfoBool(string $info, bool $default = false): bool { + if (!key_exists($info, $this->info)) { + return $default; + } + + return $this->info[$info]; + } + + /** + * Get all info. + * + * @since 15.0.0 + * + * @return array + */ + final public function getInfoAll(): array { + $info = []; + foreach ($this->info as $k => $v) { + if (substr($k, 0, 1) === '_') { + continue; + } + + $info[$k] = $v; + } + + return $info; + } + + + /** + * @since 15.0.0 + * + * On some version of PHP, it is better to force destruct the object. + * And during the index, the number of generated IIndexDocument can be + * _huge_. + */ + public function __destruct() { + unset($this->id); + unset($this->providerId); + unset($this->access); + unset($this->modifiedTime); + unset($this->title); + unset($this->content); + unset($this->hash); + unset($this->link); + unset($this->source); + unset($this->tags); + unset($this->metaTags); + unset($this->subTags); + unset($this->more); + unset($this->excerpts); + unset($this->score); + unset($this->info); + unset($this->contentEncoded); + } + + /** + * @since 15.0.0 + * + * @return array + */ + public function jsonSerialize() { + return [ + 'id' => $this->getId(), + 'providerId' => $this->getProviderId(), + 'access' => $this->access, + 'modifiedTime' => $this->getModifiedTime(), + 'title' => $this->getTitle(), + 'link' => $this->getLink(), + 'index' => $this->index, + 'source' => $this->getSource(), + 'info' => $this->getInfoAll(), + 'hash' => $this->getHash(), + 'contentSize' => $this->getContentSize(), + 'tags' => $this->getTags(), + 'metatags' => $this->getMetaTags(), + 'subtags' => $this->getSubTags(), + 'more' => $this->getMore(), + 'excerpts' => $this->getExcerpts(), + 'score' => $this->getScore() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchOption.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchOption.php new file mode 100644 index 0000000..1ff3fbb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchOption.php @@ -0,0 +1,285 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch\Model; + +use JsonSerializable; +use OCP\FullTextSearch\Model\ISearchOption; + +/** + * @since 15.0.0 + * + * Class ISearchOption + * + * @package OC\FullTextSearch\Model + */ +final class SearchOption implements ISearchOption, JsonSerializable { + + + /** @var string */ + private $name = ''; + + /** @var string */ + private $title = ''; + + /** @var string */ + private $type = ''; + + /** @var string */ + private $size = ''; + + /** @var string */ + private $placeholder = ''; + + + /** + * * + * + * The array can be empty in case no search options are available. + * The format of the array must be like this: + * + * [ + * 'panel' => [ + * 'options' => [ + * OPTION1, + * OPTION2, + * OPTION3 + * ] + * ], + * 'navigation' => [ + * 'icon' => 'css-class-of-the-icon', + * 'options' => [ + * OPTION1, + * OPTION2, + * OPTION3 + * ] + * ] + * ] + * + * - PANEL contains entries that will be displayed in the app itself, when + * a search is initiated. + * - NAVIGATION contains entries that will be available when using the + * FullTextSearch navigation page + * - OPTION is an element that define each option available to the user. + * + * The format for the options must be like this: + * + * [ + * 'name' => 'name_of_the_option', + * 'title' => 'Name displayed in the panel', + * 'type' => '', + * 'size' => '' (optional), + * 'placeholder' => '' (optional) + * ] + * + * - NAME is the variable name that is sent to the IFullTextSearchProvider + * when a ISearchRequest is requested. (keys in the array returned by the + * ISearchRequest->getOptions()) + * - TYPE can be 'input' or 'checkbox' + * - SIZE is only used in case TYPE='input', default is 'large' but can be + * 'small' + * - PLACEHOLDER is only used in case TYPE='input', default is empty. + */ + + /** + * ISearchOption constructor. + * + * Some value can be setduring the creation of the object. + * + * @since 15.0.0 + * + * @param string $name + * @param string $title + * @param string $type + * @param string $size + * @param string $placeholder + */ + public function __construct(string $name = '', string $title = '', string $type = '', string $size = '', string $placeholder = '') { + $this->name = $name; + $this->title = $title; + $this->type = $type; + $this->size = $size; + $this->placeholder = $placeholder; + } + + + /** + * Set the name/key of the option. + * The string should only contains alphanumerical chars and underscore. + * The key can be retrieve when using ISearchRequest::getOption + * + * @see ISearchRequest::getOption + * + * @since 15.0.0 + * + * @param string $name + * + * @return ISearchOption + */ + public function setName(string $name): ISearchOption { + $this->name = $name; + + return $this; + } + + /** + * Get the name/key of the option. + * + * @since 15.0.0 + * + * @return string + */ + public function getName(): string { + return $this->name; + } + + + /** + * Set the title/display name of the option. + * + * @since 15.0.0 + * + * @param string $title + * + * @return ISearchOption + */ + public function setTitle(string $title): ISearchOption { + $this->title = $title; + + return $this; + } + + /** + * Get the title of the option. + * + * @since 15.0.0 + * + * @return string + */ + public function getTitle(): string { + return $this->title; + } + + + /** + * Set the type of the option. + * $type can be ISearchOption::CHECKBOX or ISearchOption::INPUT + * + * @since 15.0.0 + * + * @param string $type + * + * @return ISearchOption + */ + public function setType(string $type): ISearchOption { + $this->type = $type; + + return $this; + } + + /** + * Get the type of the option. + * + * @since 15.0.0 + * + * @return string + */ + public function getType(): string { + return $this->type; + } + + + /** + * In case of Type is INPUT, set the size of the input field. + * Value can be ISearchOption::INPUT_SMALL or not defined. + * + * @since 15.0.0 + * + * @param string $size + * + * @return ISearchOption + */ + public function setSize(string $size): ISearchOption { + $this->size = $size; + + return $this; + } + + /** + * Get the size of the INPUT. + * + * @since 15.0.0 + * + * @return string + */ + public function getSize(): string { + return $this->size; + } + + + /** + * In case of Type is , set the placeholder to be displayed in the input + * field. + * + * @since 15.0.0 + * + * @param string $placeholder + * + * @return ISearchOption + */ + public function setPlaceholder(string $placeholder): ISearchOption { + $this->placeholder = $placeholder; + + return $this; + } + + /** + * Get the placeholder. + * + * @since 15.0.0 + * + * @return string + */ + public function getPlaceholder(): string { + return $this->placeholder; + } + + + /** + * @since 15.0.0 + * + * @return array + */ + public function jsonSerialize(): array { + return [ + 'name' => $this->getName(), + 'title' => $this->getTitle(), + 'type' => $this->getType(), + 'size' => $this->getSize(), + 'placeholder' => $this->getPlaceholder() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php new file mode 100644 index 0000000..c015c4c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php @@ -0,0 +1,181 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch\Model; + +use JsonSerializable; +use OCP\FullTextSearch\Model\ISearchRequestSimpleQuery; + +/** + * @since 17.0.0 + * + * Class SearchRequestSimpleQuery + * + * @package OC\FullTextSearch\Model + */ +final class SearchRequestSimpleQuery implements ISearchRequestSimpleQuery, JsonSerializable { + + + /** @var int */ + private $type = 0; + + /** @var string */ + private $field = ''; + + /** @var array */ + private $values = []; + + + /** + * SearchRequestQuery constructor. + * + * @param $type + * @param $field + * + * @since 17.0.0 + */ + public function __construct(string $field, int $type) { + $this->field = $field; + $this->type = $type; + } + + + /** + * Get the compare type of the query + * + * @return int + * @since 17.0.0 + */ + public function getType(): int { + return $this->type; + } + + + /** + * Get the field to apply query + * + * @return string + * @since 17.0.0 + */ + public function getField(): string { + return $this->field; + } + + /** + * Set the field to apply query + * + * @param string $field + * + * @return ISearchRequestSimpleQuery + * @since 17.0.0 + */ + public function setField(string $field): ISearchRequestSimpleQuery { + $this->field = $field; + + return $this; + } + + + /** + * Get the value to compare (string) + * + * @return array + * @since 17.0.0 + */ + public function getValues(): array { + return $this->values; + } + + + /** + * Add value to compare (string) + * + * @param string $value + * + * @return ISearchRequestSimpleQuery + * @since 17.0.0 + */ + public function addValue(string $value): ISearchRequestSimpleQuery { + $this->values[] = $value; + + return $this; + } + + /** + * Add value to compare (int) + * + * @param int $value + * + * @return ISearchRequestSimpleQuery + * @since 17.0.0 + */ + public function addValueInt(int $value): ISearchRequestSimpleQuery { + $this->values[] = $value; + + return $this; + } + + /** + * Add value to compare (array) + * + * @param array $value + * + * @return ISearchRequestSimpleQuery + * @since 17.0.0 + */ + public function addValueArray(array $value): ISearchRequestSimpleQuery { + $this->values[] = $value; + + return $this; + } + + /** + * Add value to compare (bool) + * + * @param bool $value + * + * @return ISearchRequestSimpleQuery + * @since 17.0.0 + */ + public function addValueBool(bool $value): ISearchRequestSimpleQuery { + $this->values[] = $value; + + return $this; + } + + + /** + * @return array|mixed + * @since 17.0.0 + */ + public function jsonSerialize() { + return [ + 'type' => $this->getType(), + 'field' => $this->getField(), + 'values' => $this->getValues() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchTemplate.php b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchTemplate.php new file mode 100644 index 0000000..4940e57 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/FullTextSearch/Model/SearchTemplate.php @@ -0,0 +1,253 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\FullTextSearch\Model; + +use JsonSerializable; +use OCP\FullTextSearch\IFullTextSearchProvider; +use OCP\FullTextSearch\Model\ISearchOption; +use OCP\FullTextSearch\Model\ISearchTemplate; + +/** + * Class ISearchTemplate + * + * This is a data transfer object that should be created by Content Provider + * when the getSearchTemplate() method is called. + * + * The object will contain templates to be displayed, and the list of the different + * options to be available to the user when he start a new search. + * + * The display of the Options is generated by the FullTextSearch app and Options + * can be displayed in 2 places: + * + * - the navigation page of the app that generate the indexed content. + * (files, bookmarks, deck, mails, ...) + * - the navigation page of the FullTextSearch app. + * + * Both pages will have different Options, and only the first one can integrate + * a specific template. + * + * @see IFullTextSearchProvider::getSearchTemplate + * + * @since 15.0.0 + * + * @package OC\FullTextSearch\Model + */ +final class SearchTemplate implements ISearchTemplate, JsonSerializable { + + + /** @var string */ + private $icon = ''; + + /** @var string */ + private $css = ''; + + /** @var string */ + private $template = ''; + + /** @var SearchOption[] */ + private $panelOptions = []; + + /** @var SearchOption[] */ + private $navigationOptions = []; + + + /** + * ISearchTemplate constructor. + * + * the class of the icon and the css file to be loaded can be set during the + * creation of the object. + * + * @since 15.0.0 + * + * @param string $icon + * @param string $css + */ + public function __construct(string $icon = '', string $css = '') { + $this->icon = $icon; + $this->css = $css; + } + + + /** + * Set the class of the icon to be displayed in the left panel of the + * FullTextSearch navigation page, in front of the related Content Provider. + * + * @since 15.0.0 + * + * @param string $class + * + * @return ISearchTemplate + */ + public function setIcon(string $class): ISearchTemplate { + $this->icon = $class; + + return $this; + } + + /** + * Get the class of the icon. + * + * @since 15.0.0 + * + * @return string + */ + public function getIcon(): string { + return $this->icon; + } + + + /** + * Set the path of a CSS file that will be loaded when needed. + * + * @since 15.0.0 + * + * @param string $css + * + * @return ISearchTemplate + */ + public function setCss(string $css): ISearchTemplate { + $this->css = $css; + + return $this; + } + + /** + * Get the path of the CSS file. + * + * @since 15.0.0 + * + * @return string + */ + public function getCss(): string { + return $this->css; + } + + + /** + * Set the path of the file of a template that the HTML will be displayed + * below the Options. + * This should only be used if your Content Provider needs to set options in + * a way not generated by FullTextSearch + * + * @since 15.0.0 + * + * @param string $template + * + * @return ISearchTemplate + */ + public function setTemplate(string $template): ISearchTemplate { + $this->template = $template; + + return $this; + } + + /** + * Get the path of the template file. + * + * @since 15.0.0 + * + * @return string + */ + public function getTemplate(): string { + return $this->template; + } + + + /** + * Add an option in the Panel that is displayed when the user start a search + * within the app that generate the content. + * + * @see ISearchOption + * + * @since 15.0.0 + * + * @param ISearchOption $option + * + * @return ISearchTemplate + */ + public function addPanelOption(ISearchOption $option): ISearchTemplate { + $this->panelOptions[] = $option; + + return $this; + } + + /** + * Get all options to be displayed in the Panel. + * + * @since 15.0.0 + * + * @return SearchOption[] + */ + public function getPanelOptions(): array { + return $this->panelOptions; + } + + + /** + * Add an option in the left panel of the FullTextSearch navigation page. + * + * @see ISearchOption + * + * @since 15.0.0 + * + * @param ISearchOption $option + * + * @return ISearchTemplate + */ + public function addNavigationOption(ISearchOption $option): ISearchTemplate { + $this->navigationOptions[] = $option; + + return $this; + } + + /** + * Get all options to be displayed in the FullTextSearch navigation page. + * + * @since 15.0.0 + * + * @return array + */ + public function getNavigationOptions(): array { + return $this->navigationOptions; + } + + + /** + * @since 15.0.0 + * + * @return array + */ + public function jsonSerialize(): array { + return [ + 'icon' => $this->getIcon(), + 'css' => $this->getCss(), + 'template' => $this->getTemplate(), + 'panel' => $this->getPanelOptions(), + 'navigation' => $this->getNavigationOptions() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/GlobalScale/Config.php b/docker/overlays/nextcloud/html/lib/private/GlobalScale/Config.php new file mode 100644 index 0000000..d4bde44 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/GlobalScale/Config.php @@ -0,0 +1,70 @@ + + * + * @author Bjoern Schiessle + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\GlobalScale; + +use OCP\IConfig; + +class Config implements \OCP\GlobalScale\IConfig { + + /** @var IConfig */ + private $config; + + /** + * Config constructor. + * + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * check if global scale is enabled + * + * @since 12.0.1 + * @return bool + */ + public function isGlobalScaleEnabled() { + $enabled = $this->config->getSystemValue('gs.enabled', false); + return $enabled !== false; + } + + /** + * check if federation should only be used internally in a global scale setup + * + * @since 12.0.1 + * @return bool + */ + public function onlyInternalFederation() { + // if global scale is disabled federation works always globally + $gsEnabled = $this->isGlobalScaleEnabled(); + if ($gsEnabled === false) { + return false; + } + + $enabled = $this->config->getSystemValue('gs.federation', 'internal'); + + return $enabled === 'internal'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Group/Backend.php b/docker/overlays/nextcloud/html/lib/private/Group/Backend.php new file mode 100644 index 0000000..ebd8d36 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Group/Backend.php @@ -0,0 +1,135 @@ + + * @author Knut Ahlers + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Group; + +/** + * Abstract base class for user management + */ +abstract class Backend implements \OCP\GroupInterface { + /** + * error code for functions not provided by the group backend + */ + public const NOT_IMPLEMENTED = -501; + + protected $possibleActions = [ + self::CREATE_GROUP => 'createGroup', + self::DELETE_GROUP => 'deleteGroup', + self::ADD_TO_GROUP => 'addToGroup', + self::REMOVE_FROM_GOUP => 'removeFromGroup', + self::COUNT_USERS => 'countUsersInGroup', + self::GROUP_DETAILS => 'getGroupDetails', + self::IS_ADMIN => 'isAdmin', + ]; + + /** + * Get all supported actions + * @return int bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with \OC\Group\Backend::CREATE_GROUP etc. + */ + public function getSupportedActions() { + $actions = 0; + foreach ($this->possibleActions as $action => $methodName) { + if (method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return bool + * + * Returns the supported actions as int to be + * compared with \OC\Group\Backend::CREATE_GROUP etc. + */ + public function implementsActions($actions) { + return (bool)($this->getSupportedActions() & $actions); + } + + /** + * is user in group? + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + return in_array($gid, $this->getUserGroups($uid)); + } + + /** + * Get all groups a user belongs to + * @param string $uid Name of the user + * @return array an array of group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + return []; + } + + /** + * get a list of all groups + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of group names + * + * Returns a list with all groups + */ + + public function getGroups($search = '', $limit = -1, $offset = 0) { + return []; + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return in_array($gid, $this->getGroups($gid, 1)); + } + + /** + * get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + return []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Group/Database.php b/docker/overlays/nextcloud/html/lib/private/Group/Database.php new file mode 100644 index 0000000..97094c6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Group/Database.php @@ -0,0 +1,505 @@ + + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Loki3000 + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `groups` ( + * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * PRIMARY KEY (`gid`) + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + * CREATE TABLE `group_user` ( + * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + */ + +namespace OC\Group; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Group\Backend\ABackend; +use OCP\Group\Backend\IAddToGroupBackend; +use OCP\Group\Backend\ICountDisabledInGroup; +use OCP\Group\Backend\ICountUsersBackend; +use OCP\Group\Backend\ICreateGroupBackend; +use OCP\Group\Backend\IDeleteGroupBackend; +use OCP\Group\Backend\IGetDisplayNameBackend; +use OCP\Group\Backend\IGroupDetailsBackend; +use OCP\Group\Backend\IRemoveFromGroupBackend; +use OCP\Group\Backend\ISetDisplayNameBackend; +use OCP\IDBConnection; + +/** + * Class for group management in a SQL Database (e.g. MySQL, SQLite) + */ +class Database extends ABackend implements + IAddToGroupBackend, + ICountDisabledInGroup, + ICountUsersBackend, + ICreateGroupBackend, + IDeleteGroupBackend, + IGetDisplayNameBackend, + IGroupDetailsBackend, + IRemoveFromGroupBackend, + ISetDisplayNameBackend { + + /** @var string[] */ + private $groupCache = []; + + /** @var IDBConnection */ + private $dbConn; + + /** + * \OC\Group\Database constructor. + * + * @param IDBConnection|null $dbConn + */ + public function __construct(IDBConnection $dbConn = null) { + $this->dbConn = $dbConn; + } + + /** + * FIXME: This function should not be required! + */ + private function fixDI() { + if ($this->dbConn === null) { + $this->dbConn = \OC::$server->getDatabaseConnection(); + } + } + + /** + * Try to create a new group + * @param string $gid The name of the group to create + * @return bool + * + * Tries to create a new group. If the group name already exists, false will + * be returned. + */ + public function createGroup(string $gid): bool { + $this->fixDI(); + + try { + // Add group + $builder = $this->dbConn->getQueryBuilder(); + $result = $builder->insert('groups') + ->setValue('gid', $builder->createNamedParameter($gid)) + ->setValue('displayname', $builder->createNamedParameter($gid)) + ->execute(); + } catch (UniqueConstraintViolationException $e) { + $result = 0; + } + + // Add to cache + $this->groupCache[$gid] = [ + 'gid' => $gid, + 'displayname' => $gid + ]; + + return $result === 1; + } + + /** + * delete a group + * @param string $gid gid of the group to delete + * @return bool + * + * Deletes a group and removes it from the group_user-table + */ + public function deleteGroup(string $gid): bool { + $this->fixDI(); + + // Delete the group + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('groups') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->execute(); + + // Delete the group-user relation + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('group_user') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->execute(); + + // Delete the group-groupadmin relation + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('group_admin') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->execute(); + + // Delete from cache + unset($this->groupCache[$gid]); + + return true; + } + + /** + * is user in group? + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + $this->fixDI(); + + // check + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select('uid') + ->from('group_user') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->execute(); + + $result = $cursor->fetch(); + $cursor->closeCursor(); + + return $result ? true : false; + } + + /** + * Add a user to a group + * @param string $uid Name of the user to add to group + * @param string $gid Name of the group in which add the user + * @return bool + * + * Adds a user to a group. + */ + public function addToGroup(string $uid, string $gid): bool { + $this->fixDI(); + + // No duplicate entries! + if (!$this->inGroup($uid, $gid)) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->insert('group_user') + ->setValue('uid', $qb->createNamedParameter($uid)) + ->setValue('gid', $qb->createNamedParameter($gid)) + ->execute(); + return true; + } else { + return false; + } + } + + /** + * Removes a user from a group + * @param string $uid Name of the user to remove from group + * @param string $gid Name of the group from which remove the user + * @return bool + * + * removes the user from a group. + */ + public function removeFromGroup(string $uid, string $gid): bool { + $this->fixDI(); + + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('group_user') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->execute(); + + return true; + } + + /** + * Get all groups a user belongs to + * @param string $uid Name of the user + * @return array an array of group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + //guests has empty or null $uid + if ($uid === null || $uid === '') { + return []; + } + + $this->fixDI(); + + // No magic! + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select('gu.gid', 'g.displayname') + ->from('group_user', 'gu') + ->leftJoin('gu', 'groups', 'g', $qb->expr()->eq('gu.gid', 'g.gid')) + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->execute(); + + $groups = []; + while ($row = $cursor->fetch()) { + $groups[] = $row['gid']; + $this->groupCache[$row['gid']] = [ + 'gid' => $row['gid'], + 'displayname' => $row['displayname'], + ]; + } + $cursor->closeCursor(); + + return $groups; + } + + /** + * get a list of all groups + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = null, $offset = null) { + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select('gid') + ->from('groups') + ->orderBy('gid', 'ASC'); + + if ($search !== '') { + $query->where($query->expr()->iLike('gid', $query->createNamedParameter( + '%' . $this->dbConn->escapeLikeParameter($search) . '%' + ))); + $query->orWhere($query->expr()->iLike('displayname', $query->createNamedParameter( + '%' . $this->dbConn->escapeLikeParameter($search) . '%' + ))); + } + + $query->setMaxResults($limit) + ->setFirstResult($offset); + $result = $query->execute(); + + $groups = []; + while ($row = $result->fetch()) { + $groups[] = $row['gid']; + } + $result->closeCursor(); + + return $groups; + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + $this->fixDI(); + + // Check cache first + if (isset($this->groupCache[$gid])) { + return true; + } + + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select('gid', 'displayname') + ->from('groups') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) + ->execute(); + $result = $cursor->fetch(); + $cursor->closeCursor(); + + if ($result !== false) { + $this->groupCache[$gid] = [ + 'gid' => $gid, + 'displayname' => $result['displayname'], + ]; + return true; + } + return false; + } + + /** + * get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select('g.uid') + ->from('group_user', 'g') + ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))) + ->orderBy('g.uid', 'ASC'); + + if ($search !== '') { + $query->leftJoin('g', 'users', 'u', $query->expr()->eq('g.uid', 'u.uid')) + ->leftJoin('u', 'preferences', 'p', $query->expr()->andX( + $query->expr()->eq('p.userid', 'u.uid'), + $query->expr()->eq('p.appid', $query->expr()->literal('settings')), + $query->expr()->eq('p.configkey', $query->expr()->literal('email'))) + ) + // sqlite doesn't like re-using a single named parameter here + ->andWhere( + $query->expr()->orX( + $query->expr()->ilike('g.uid', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')), + $query->expr()->ilike('u.displayname', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')), + $query->expr()->ilike('p.configvalue', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')) + ) + ) + ->orderBy('u.uid_lower', 'ASC'); + } + + if ($limit !== -1) { + $query->setMaxResults($limit); + } + if ($offset !== 0) { + $query->setFirstResult($offset); + } + + $result = $query->execute(); + + $users = []; + while ($row = $result->fetch()) { + $users[] = $row['uid']; + } + $result->closeCursor(); + + return $users; + } + + /** + * get the number of all users matching the search string in a group + * @param string $gid + * @param string $search + * @return int + */ + public function countUsersInGroup(string $gid, string $search = ''): int { + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select($query->func()->count('*', 'num_users')) + ->from('group_user') + ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))); + + if ($search !== '') { + $query->andWhere($query->expr()->like('uid', $query->createNamedParameter( + '%' . $this->dbConn->escapeLikeParameter($search) . '%' + ))); + } + + $result = $query->execute(); + $count = $result->fetchColumn(); + $result->closeCursor(); + + if ($count !== false) { + $count = (int)$count; + } else { + $count = 0; + } + + return $count; + } + + /** + * get the number of disabled users in a group + * + * @param string $search + * + * @return int + */ + public function countDisabledInGroup(string $gid): int { + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select($query->createFunction('COUNT(DISTINCT ' . $query->getColumnName('uid') . ')')) + ->from('preferences', 'p') + ->innerJoin('p', 'group_user', 'g', $query->expr()->eq('p.userid', 'g.uid')) + ->where($query->expr()->eq('appid', $query->createNamedParameter('core'))) + ->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('enabled'))) + ->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter('false'), IQueryBuilder::PARAM_STR)) + ->andWhere($query->expr()->eq('gid', $query->createNamedParameter($gid), IQueryBuilder::PARAM_STR)); + + $result = $query->execute(); + $count = $result->fetchColumn(); + $result->closeCursor(); + + if ($count !== false) { + $count = (int)$count; + } else { + $count = 0; + } + + return $count; + } + + public function getDisplayName(string $gid): string { + if (isset($this->groupCache[$gid])) { + return $this->groupCache[$gid]['displayname']; + } + + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select('displayname') + ->from('groups') + ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))); + + $result = $query->execute(); + $displayName = $result->fetchColumn(); + $result->closeCursor(); + + return (string) $displayName; + } + + public function getGroupDetails(string $gid): array { + $displayName = $this->getDisplayName($gid); + if ($displayName !== '') { + return ['displayName' => $displayName]; + } + + return []; + } + + public function setDisplayName(string $gid, string $displayName): bool { + if (!$this->groupExists($gid)) { + return false; + } + + $this->fixDI(); + + $displayName = trim($displayName); + if ($displayName === '') { + $displayName = $gid; + } + + $query = $this->dbConn->getQueryBuilder(); + $query->update('groups') + ->set('displayname', $query->createNamedParameter($displayName)) + ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))); + $query->execute(); + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Group/Group.php b/docker/overlays/nextcloud/html/lib/private/Group/Group.php new file mode 100644 index 0000000..2e16d5f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Group/Group.php @@ -0,0 +1,405 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Group; + +use OC\Hooks\PublicEmitter; +use OCP\Group\Backend\ICountDisabledInGroup; +use OCP\Group\Backend\IGetDisplayNameBackend; +use OCP\Group\Backend\IHideFromCollaborationBackend; +use OCP\Group\Backend\ISetDisplayNameBackend; +use OCP\GroupInterface; +use OCP\IGroup; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Group implements IGroup { + /** @var null|string */ + protected $displayName; + + /** @var string */ + private $gid; + + /** @var \OC\User\User[] */ + private $users = []; + + /** @var bool */ + private $usersLoaded; + + /** @var Backend[] */ + private $backends; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** @var \OC\User\Manager|IUserManager */ + private $userManager; + /** @var PublicEmitter */ + private $emitter; + + + /** + * @param string $gid + * @param Backend[] $backends + * @param EventDispatcherInterface $dispatcher + * @param IUserManager $userManager + * @param PublicEmitter $emitter + * @param string $displayName + */ + public function __construct(string $gid, array $backends, EventDispatcherInterface $dispatcher, IUserManager $userManager, PublicEmitter $emitter = null, ?string $displayName = null) { + $this->gid = $gid; + $this->backends = $backends; + $this->dispatcher = $dispatcher; + $this->userManager = $userManager; + $this->emitter = $emitter; + $this->displayName = $displayName; + } + + public function getGID() { + return $this->gid; + } + + public function getDisplayName() { + if (is_null($this->displayName)) { + foreach ($this->backends as $backend) { + if ($backend instanceof IGetDisplayNameBackend) { + $displayName = $backend->getDisplayName($this->gid); + if (trim($displayName) !== '') { + $this->displayName = $displayName; + return $this->displayName; + } + } + } + return $this->gid; + } + return $this->displayName; + } + + public function setDisplayName(string $displayName): bool { + $displayName = trim($displayName); + if ($displayName !== '') { + foreach ($this->backends as $backend) { + if (($backend instanceof ISetDisplayNameBackend) + && $backend->setDisplayName($this->gid, $displayName)) { + $this->displayName = $displayName; + return true; + } + } + } + return false; + } + + /** + * get all users in the group + * + * @return \OC\User\User[] + */ + public function getUsers() { + if ($this->usersLoaded) { + return $this->users; + } + + $userIds = []; + foreach ($this->backends as $backend) { + $diff = array_diff( + $backend->usersInGroup($this->gid), + $userIds + ); + if ($diff) { + $userIds = array_merge($userIds, $diff); + } + } + + $this->users = $this->getVerifiedUsers($userIds); + $this->usersLoaded = true; + return $this->users; + } + + /** + * check if a user is in the group + * + * @param IUser $user + * @return bool + */ + public function inGroup(IUser $user) { + if (isset($this->users[$user->getUID()])) { + return true; + } + foreach ($this->backends as $backend) { + if ($backend->inGroup($user->getUID(), $this->gid)) { + $this->users[$user->getUID()] = $user; + return true; + } + } + return false; + } + + /** + * add a user to the group + * + * @param IUser $user + */ + public function addUser(IUser $user) { + if ($this->inGroup($user)) { + return; + } + + $this->dispatcher->dispatch(IGroup::class . '::preAddUser', new GenericEvent($this, [ + 'user' => $user, + ])); + + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preAddUser', [$this, $user]); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\Group\Backend::ADD_TO_GROUP)) { + $backend->addToGroup($user->getUID(), $this->gid); + if ($this->users) { + $this->users[$user->getUID()] = $user; + } + + $this->dispatcher->dispatch(IGroup::class . '::postAddUser', new GenericEvent($this, [ + 'user' => $user, + ])); + + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'postAddUser', [$this, $user]); + } + return; + } + } + } + + /** + * remove a user from the group + * + * @param \OC\User\User $user + */ + public function removeUser($user) { + $result = false; + $this->dispatcher->dispatch(IGroup::class . '::preRemoveUser', new GenericEvent($this, [ + 'user' => $user, + ])); + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preRemoveUser', [$this, $user]); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\Group\Backend::REMOVE_FROM_GOUP) and $backend->inGroup($user->getUID(), $this->gid)) { + $backend->removeFromGroup($user->getUID(), $this->gid); + $result = true; + } + } + if ($result) { + $this->dispatcher->dispatch(IGroup::class . '::postRemoveUser', new GenericEvent($this, [ + 'user' => $user, + ])); + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'postRemoveUser', [$this, $user]); + } + if ($this->users) { + foreach ($this->users as $index => $groupUser) { + if ($groupUser->getUID() === $user->getUID()) { + unset($this->users[$index]); + return; + } + } + } + } + } + + /** + * search for users in the group by userid + * + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchUsers($search, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset); + $users += $this->getVerifiedUsers($userIds); + if (!is_null($limit) and $limit <= 0) { + return $users; + } + } + return $users; + } + + /** + * returns the number of users matching the search string + * + * @param string $search + * @return int|bool + */ + public function count($search = '') { + $users = false; + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) { + if ($users === false) { + //we could directly add to a bool variable, but this would + //be ugly + $users = 0; + } + $users += $backend->countUsersInGroup($this->gid, $search); + } + } + return $users; + } + + /** + * returns the number of disabled users + * + * @return int|bool + */ + public function countDisabled() { + $users = false; + foreach ($this->backends as $backend) { + if ($backend instanceof ICountDisabledInGroup) { + if ($users === false) { + //we could directly add to a bool variable, but this would + //be ugly + $users = 0; + } + $users += $backend->countDisabledInGroup($this->gid); + } + } + return $users; + } + + /** + * search for users in the group by displayname + * + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchDisplayName($search, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset); + $users = $this->getVerifiedUsers($userIds); + if (!is_null($limit) and $limit <= 0) { + return array_values($users); + } + } + return array_values($users); + } + + /** + * delete the group + * + * @return bool + */ + public function delete() { + // Prevent users from deleting group admin + if ($this->getGID() === 'admin') { + return false; + } + + $result = false; + $this->dispatcher->dispatch(IGroup::class . '::preDelete', new GenericEvent($this)); + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preDelete', [$this]); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\Group\Backend::DELETE_GROUP)) { + $result = true; + $backend->deleteGroup($this->gid); + } + } + if ($result) { + $this->dispatcher->dispatch(IGroup::class . '::postDelete', new GenericEvent($this)); + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'postDelete', [$this]); + } + } + return $result; + } + + /** + * returns all the Users from an array that really exists + * @param string[] $userIds an array containing user IDs + * @return \OC\User\User[] an Array with the userId as Key and \OC\User\User as value + */ + private function getVerifiedUsers($userIds) { + if (!is_array($userIds)) { + return []; + } + $users = []; + foreach ($userIds as $userId) { + $user = $this->userManager->get($userId); + if (!is_null($user)) { + $users[$userId] = $user; + } + } + return $users; + } + + /** + * @return bool + * @since 14.0.0 + */ + public function canRemoveUser() { + foreach ($this->backends as $backend) { + if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) { + return true; + } + } + return false; + } + + /** + * @return bool + * @since 14.0.0 + */ + public function canAddUser() { + foreach ($this->backends as $backend) { + if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) { + return true; + } + } + return false; + } + + /** + * @return bool + * @since 16.0.0 + */ + public function hideFromCollaboration(): bool { + return array_reduce($this->backends, function (bool $hide, GroupInterface $backend) { + return $hide | ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid)); + }, false); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Group/Manager.php b/docker/overlays/nextcloud/html/lib/private/Group/Manager.php new file mode 100644 index 0000000..6056bcd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Group/Manager.php @@ -0,0 +1,425 @@ + + * @author Bart Visscher + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Knut Ahlers + * @author Lukas Reschke + * @author macjohnny + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Roman Kreisel + * @author Thomas Müller + * @author Vincent Petry + * @author Vinicius Cubas Brand + * @author voxsim "Simon Vocella" + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Group; + +use OC\Hooks\PublicEmitter; +use OCP\GroupInterface; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Class Manager + * + * Hooks available in scope \OC\Group: + * - preAddUser(\OC\Group\Group $group, \OC\User\User $user) + * - postAddUser(\OC\Group\Group $group, \OC\User\User $user) + * - preRemoveUser(\OC\Group\Group $group, \OC\User\User $user) + * - postRemoveUser(\OC\Group\Group $group, \OC\User\User $user) + * - preDelete(\OC\Group\Group $group) + * - postDelete(\OC\Group\Group $group) + * - preCreate(string $groupId) + * - postCreate(\OC\Group\Group $group) + * + * @package OC\Group + */ +class Manager extends PublicEmitter implements IGroupManager { + /** @var GroupInterface[] */ + private $backends = []; + + /** @var \OC\User\Manager */ + private $userManager; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** @var ILogger */ + private $logger; + + /** @var \OC\Group\Group[] */ + private $cachedGroups = []; + + /** @var (string[])[] */ + private $cachedUserGroups = []; + + /** @var \OC\SubAdmin */ + private $subAdmin = null; + + /** + * @param \OC\User\Manager $userManager + * @param EventDispatcherInterface $dispatcher + * @param ILogger $logger + */ + public function __construct(\OC\User\Manager $userManager, + EventDispatcherInterface $dispatcher, + ILogger $logger) { + $this->userManager = $userManager; + $this->dispatcher = $dispatcher; + $this->logger = $logger; + + $cachedGroups = &$this->cachedGroups; + $cachedUserGroups = &$this->cachedUserGroups; + $this->listen('\OC\Group', 'postDelete', function ($group) use (&$cachedGroups, &$cachedUserGroups) { + /** + * @var \OC\Group\Group $group + */ + unset($cachedGroups[$group->getGID()]); + $cachedUserGroups = []; + }); + $this->listen('\OC\Group', 'postAddUser', function ($group) use (&$cachedUserGroups) { + /** + * @var \OC\Group\Group $group + */ + $cachedUserGroups = []; + }); + $this->listen('\OC\Group', 'postRemoveUser', function ($group) use (&$cachedUserGroups) { + /** + * @var \OC\Group\Group $group + */ + $cachedUserGroups = []; + }); + } + + /** + * Checks whether a given backend is used + * + * @param string $backendClass Full classname including complete namespace + * @return bool + */ + public function isBackendUsed($backendClass) { + $backendClass = strtolower(ltrim($backendClass, '\\')); + + foreach ($this->backends as $backend) { + if (strtolower(get_class($backend)) === $backendClass) { + return true; + } + } + + return false; + } + + /** + * @param \OCP\GroupInterface $backend + */ + public function addBackend($backend) { + $this->backends[] = $backend; + $this->clearCaches(); + } + + public function clearBackends() { + $this->backends = []; + $this->clearCaches(); + } + + /** + * Get the active backends + * + * @return \OCP\GroupInterface[] + */ + public function getBackends() { + return $this->backends; + } + + + protected function clearCaches() { + $this->cachedGroups = []; + $this->cachedUserGroups = []; + } + + /** + * @param string $gid + * @return IGroup|null + */ + public function get($gid) { + if (isset($this->cachedGroups[$gid])) { + return $this->cachedGroups[$gid]; + } + return $this->getGroupObject($gid); + } + + /** + * @param string $gid + * @param string $displayName + * @return \OCP\IGroup|null + */ + protected function getGroupObject($gid, $displayName = null) { + $backends = []; + foreach ($this->backends as $backend) { + if ($backend->implementsActions(Backend::GROUP_DETAILS)) { + $groupData = $backend->getGroupDetails($gid); + if (is_array($groupData) && !empty($groupData)) { + // take the display name from the first backend that has a non-null one + if (is_null($displayName) && isset($groupData['displayName'])) { + $displayName = $groupData['displayName']; + } + $backends[] = $backend; + } + } elseif ($backend->groupExists($gid)) { + $backends[] = $backend; + } + } + if (count($backends) === 0) { + return null; + } + $this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName); + return $this->cachedGroups[$gid]; + } + + /** + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return $this->get($gid) instanceof IGroup; + } + + /** + * @param string $gid + * @return IGroup|null + */ + public function createGroup($gid) { + if ($gid === '' || $gid === null) { + return null; + } elseif ($group = $this->get($gid)) { + return $group; + } else { + $this->emit('\OC\Group', 'preCreate', [$gid]); + foreach ($this->backends as $backend) { + if ($backend->implementsActions(Backend::CREATE_GROUP)) { + if ($backend->createGroup($gid)) { + $group = $this->getGroupObject($gid); + $this->emit('\OC\Group', 'postCreate', [$group]); + return $group; + } + } + } + return null; + } + } + + /** + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\Group\Group[] + */ + public function search($search, $limit = null, $offset = null) { + $groups = []; + foreach ($this->backends as $backend) { + $groupIds = $backend->getGroups($search, $limit, $offset); + foreach ($groupIds as $groupId) { + $aGroup = $this->get($groupId); + if ($aGroup instanceof IGroup) { + $groups[$groupId] = $aGroup; + } else { + $this->logger->debug('Group "' . $groupId . '" was returned by search but not found through direct access', ['app' => 'core']); + } + } + if (!is_null($limit) and $limit <= 0) { + return array_values($groups); + } + } + return array_values($groups); + } + + /** + * @param IUser|null $user + * @return \OC\Group\Group[] + */ + public function getUserGroups(IUser $user = null) { + if (!$user instanceof IUser) { + return []; + } + return $this->getUserIdGroups($user->getUID()); + } + + /** + * @param string $uid the user id + * @return \OC\Group\Group[] + */ + public function getUserIdGroups($uid) { + $groups = []; + + foreach ($this->getUserIdGroupIds($uid) as $groupId) { + $aGroup = $this->get($groupId); + if ($aGroup instanceof IGroup) { + $groups[$groupId] = $aGroup; + } else { + $this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']); + } + } + + return $groups; + } + + /** + * Checks if a userId is in the admin group + * + * @param string $userId + * @return bool if admin + */ + public function isAdmin($userId) { + foreach ($this->backends as $backend) { + if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) { + return true; + } + } + return $this->isInGroup($userId, 'admin'); + } + + /** + * Checks if a userId is in a group + * + * @param string $userId + * @param string $group + * @return bool if in group + */ + public function isInGroup($userId, $group) { + return array_search($group, $this->getUserIdGroupIds($userId)) !== false; + } + + /** + * get a list of group ids for a user + * + * @param IUser $user + * @return array with group ids + */ + public function getUserGroupIds(IUser $user) { + return $this->getUserIdGroupIds($user->getUID()); + } + + /** + * @param string $uid the user id + * @return GroupInterface[] + */ + private function getUserIdGroupIds($uid) { + if (!isset($this->cachedUserGroups[$uid])) { + $groups = []; + foreach ($this->backends as $backend) { + if ($groupIds = $backend->getUserGroups($uid)) { + $groups = array_merge($groups, $groupIds); + } + } + $this->cachedUserGroups[$uid] = $groups; + } + + return $this->cachedUserGroups[$uid]; + } + + /** + * get an array of groupid and displayName for a user + * + * @param IUser $user + * @return array ['displayName' => displayname] + */ + public function getUserGroupNames(IUser $user) { + return array_map(function ($group) { + return ['displayName' => $group->getDisplayName()]; + }, $this->getUserGroups($user)); + } + + /** + * get a list of all display names in a group + * + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of display names (value) and user ids (key) + */ + public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $group = $this->get($gid); + if (is_null($group)) { + return []; + } + + $search = trim($search); + $groupUsers = []; + + if (!empty($search)) { + // only user backends have the capability to do a complex search for users + $searchOffset = 0; + $searchLimit = $limit * 100; + if ($limit === -1) { + $searchLimit = 500; + } + + do { + $filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset); + foreach ($filteredUsers as $filteredUser) { + if ($group->inGroup($filteredUser)) { + $groupUsers[] = $filteredUser; + } + } + $searchOffset += $searchLimit; + } while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit); + + if ($limit === -1) { + $groupUsers = array_slice($groupUsers, $offset); + } else { + $groupUsers = array_slice($groupUsers, $offset, $limit); + } + } else { + $groupUsers = $group->searchUsers('', $limit, $offset); + } + + $matchingUsers = []; + foreach ($groupUsers as $groupUser) { + $matchingUsers[(string) $groupUser->getUID()] = $groupUser->getDisplayName(); + } + return $matchingUsers; + } + + /** + * @return \OC\SubAdmin + */ + public function getSubAdmin() { + if (!$this->subAdmin) { + $this->subAdmin = new \OC\SubAdmin( + $this->userManager, + $this, + \OC::$server->getDatabaseConnection() + ); + } + + return $this->subAdmin; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Group/MetaData.php b/docker/overlays/nextcloud/html/lib/private/Group/MetaData.php new file mode 100644 index 0000000..1bf7481 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Group/MetaData.php @@ -0,0 +1,209 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Stephan Peijnik + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Group; + +use OC\Group\Manager as GroupManager; +use OCP\IGroupManager; +use OCP\IUserSession; + +class MetaData { + public const SORT_NONE = 0; + public const SORT_USERCOUNT = 1; // May have performance issues on LDAP backends + public const SORT_GROUPNAME = 2; + + /** @var string */ + protected $user; + /** @var bool */ + protected $isAdmin; + /** @var array */ + protected $metaData = []; + /** @var GroupManager */ + protected $groupManager; + /** @var bool */ + protected $sorting = false; + /** @var IUserSession */ + protected $userSession; + + /** + * @param string $user the uid of the current user + * @param bool $isAdmin whether the current users is an admin + * @param IGroupManager $groupManager + * @param IUserSession $userSession + */ + public function __construct( + $user, + $isAdmin, + IGroupManager $groupManager, + IUserSession $userSession + ) { + $this->user = $user; + $this->isAdmin = (bool)$isAdmin; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + } + + /** + * returns an array with meta data about all available groups + * the array is structured as follows: + * [0] array containing meta data about admin groups + * [1] array containing meta data about unprivileged groups + * @param string $groupSearch only effective when instance was created with + * isAdmin being true + * @param string $userSearch the pattern users are search for + * @return array + */ + public function get($groupSearch = '', $userSearch = '') { + $key = $groupSearch . '::' . $userSearch; + if (isset($this->metaData[$key])) { + return $this->metaData[$key]; + } + + $adminGroups = []; + $groups = []; + $sortGroupsIndex = 0; + $sortGroupsKeys = []; + $sortAdminGroupsIndex = 0; + $sortAdminGroupsKeys = []; + + foreach ($this->getGroups($groupSearch) as $group) { + $groupMetaData = $this->generateGroupMetaData($group, $userSearch); + if (strtolower($group->getGID()) !== 'admin') { + $this->addEntry( + $groups, + $sortGroupsKeys, + $sortGroupsIndex, + $groupMetaData); + } else { + //admin group is hard coded to 'admin' for now. In future, + //backends may define admin groups too. Then the if statement + //has to be adjusted accordingly. + $this->addEntry( + $adminGroups, + $sortAdminGroupsKeys, + $sortAdminGroupsIndex, + $groupMetaData); + } + } + + //whether sorting is necessary is will be checked in sort() + $this->sort($groups, $sortGroupsKeys); + $this->sort($adminGroups, $sortAdminGroupsKeys); + + $this->metaData[$key] = [$adminGroups, $groups]; + return $this->metaData[$key]; + } + + /** + * sets the sort mode, see SORT_* constants for supported modes + * + * @param int $sortMode + */ + public function setSorting($sortMode) { + switch ($sortMode) { + case self::SORT_USERCOUNT: + case self::SORT_GROUPNAME: + $this->sorting = $sortMode; + break; + + default: + $this->sorting = self::SORT_NONE; + } + } + + /** + * adds an group entry to the resulting array + * @param array $entries the resulting array, by reference + * @param array $sortKeys the sort key array, by reference + * @param int $sortIndex the sort key index, by reference + * @param array $data the group's meta data as returned by generateGroupMetaData() + */ + private function addEntry(&$entries, &$sortKeys, &$sortIndex, $data) { + $entries[] = $data; + if ($this->sorting === self::SORT_USERCOUNT) { + $sortKeys[$sortIndex] = $data['usercount']; + $sortIndex++; + } elseif ($this->sorting === self::SORT_GROUPNAME) { + $sortKeys[$sortIndex] = $data['name']; + $sortIndex++; + } + } + + /** + * creates an array containing the group meta data + * @param \OCP\IGroup $group + * @param string $userSearch + * @return array with the keys 'id', 'name', 'usercount' and 'disabled' + */ + private function generateGroupMetaData(\OCP\IGroup $group, $userSearch) { + return [ + 'id' => $group->getGID(), + 'name' => $group->getDisplayName(), + 'usercount' => $this->sorting === self::SORT_USERCOUNT ? $group->count($userSearch) : 0, + 'disabled' => $group->countDisabled(), + 'canAdd' => $group->canAddUser(), + 'canRemove' => $group->canRemoveUser(), + ]; + } + + /** + * sorts the result array, if applicable + * @param array $entries the result array, by reference + * @param array $sortKeys the array containing the sort keys + * @param return null + */ + private function sort(&$entries, $sortKeys) { + if ($this->sorting === self::SORT_USERCOUNT) { + array_multisort($sortKeys, SORT_DESC, $entries); + } elseif ($this->sorting === self::SORT_GROUPNAME) { + array_multisort($sortKeys, SORT_ASC, $entries); + } + } + + /** + * returns the available groups + * @param string $search a search string + * @return \OCP\IGroup[] + */ + public function getGroups($search = '') { + if ($this->isAdmin) { + return $this->groupManager->search($search); + } else { + $userObject = $this->userSession->getUser(); + if ($userObject !== null) { + $groups = $this->groupManager->getSubAdmin()->getSubAdminsGroups($userObject); + } else { + $groups = []; + } + + return $groups; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/HintException.php b/docker/overlays/nextcloud/html/lib/private/HintException.php new file mode 100644 index 0000000..acb8df4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/HintException.php @@ -0,0 +1,80 @@ + + * @author Bart Visscher + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +/** + * Class HintException + * + * An Exception class with the intention to be presented to the end user + * + * @package OC + */ +class HintException extends \Exception { + private $hint; + + /** + * HintException constructor. + * + * @param string $message The error message. It will be not revealed to the + * the user (unless the hint is empty) and thus + * should be not translated. + * @param string $hint A useful message that is presented to the end + * user. It should be translated, but must not + * contain sensitive data. + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($message, $hint = '', $code = 0, \Exception $previous = null) { + $this->hint = $hint; + parent::__construct($message, $code, $previous); + } + + /** + * Returns a string representation of this Exception that includes the error + * code, the message and the hint. + * + * @return string + */ + public function __toString() { + return __CLASS__ . ": [{$this->code}]: {$this->message} ({$this->hint})\n"; + } + + /** + * Returns the hint with the intention to be presented to the end user. If + * an empty hint was specified upon instatiation, the message is returned + * instead. + * + * @return string + */ + public function getHint() { + if (empty($this->hint)) { + return $this->message; + } + return $this->hint; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/BasicEmitter.php b/docker/overlays/nextcloud/html/lib/private/Hooks/BasicEmitter.php new file mode 100644 index 0000000..ce15dcb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/BasicEmitter.php @@ -0,0 +1,32 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +/** + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher + */ +abstract class BasicEmitter implements Emitter { + use EmitterTrait; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/Emitter.php b/docker/overlays/nextcloud/html/lib/private/Hooks/Emitter.php new file mode 100644 index 0000000..cd4793e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/Emitter.php @@ -0,0 +1,52 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +/** + * Class Emitter + * + * interface for all classes that are able to emit events + * + * @package OC\Hooks + * @deprecated 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service + */ +interface Emitter { + /** + * @param string $scope + * @param string $method + * @param callable $callback + * @return void + */ + public function listen($scope, $method, callable $callback); + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + * @return void + */ + public function removeListener($scope = null, $method = null, callable $callback = null); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/EmitterTrait.php b/docker/overlays/nextcloud/html/lib/private/Hooks/EmitterTrait.php new file mode 100644 index 0000000..85efa21 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/EmitterTrait.php @@ -0,0 +1,105 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +trait EmitterTrait { + + /** + * @var callable[][] $listeners + */ + protected $listeners = []; + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + $eventName = $scope . '::' . $method; + if (!isset($this->listeners[$eventName])) { + $this->listeners[$eventName] = []; + } + if (array_search($callback, $this->listeners[$eventName], true) === false) { + $this->listeners[$eventName][] = $callback; + } + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $names = []; + $allNames = array_keys($this->listeners); + if ($scope and $method) { + $name = $scope . '::' . $method; + if (isset($this->listeners[$name])) { + $names[] = $name; + } + } elseif ($scope) { + foreach ($allNames as $name) { + $parts = explode('::', $name, 2); + if ($parts[0] == $scope) { + $names[] = $name; + } + } + } elseif ($method) { + foreach ($allNames as $name) { + $parts = explode('::', $name, 2); + if ($parts[1] == $method) { + $names[] = $name; + } + } + } else { + $names = $allNames; + } + + foreach ($names as $name) { + if ($callback) { + $index = array_search($callback, $this->listeners[$name], true); + if ($index !== false) { + unset($this->listeners[$name][$index]); + } + } else { + $this->listeners[$name] = []; + } + } + } + + /** + * @param string $scope + * @param string $method + * @param array $arguments optional + */ + protected function emit($scope, $method, array $arguments = []) { + $eventName = $scope . '::' . $method; + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners[$eventName] as $callback) { + call_user_func_array($callback, $arguments); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/ForwardingEmitter.php b/docker/overlays/nextcloud/html/lib/private/Hooks/ForwardingEmitter.php new file mode 100644 index 0000000..3ac6cca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/ForwardingEmitter.php @@ -0,0 +1,66 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +/** + * Class ForwardingEmitter + * + * allows forwarding all listen calls to other emitters + * + * @package OC\Hooks + */ +abstract class ForwardingEmitter extends BasicEmitter { + /** + * @var \OC\Hooks\Emitter[] array + */ + private $forwardEmitters = []; + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + parent::listen($scope, $method, $callback); + foreach ($this->forwardEmitters as $emitter) { + $emitter->listen($scope, $method, $callback); + } + } + + /** + * @param \OC\Hooks\Emitter $emitter + */ + protected function forward(Emitter $emitter) { + $this->forwardEmitters[] = $emitter; + + //forward all previously connected hooks + foreach ($this->listeners as $key => $listeners) { + list($scope, $method) = explode('::', $key, 2); + foreach ($listeners as $listener) { + $emitter->listen($scope, $method, $listener); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/LegacyEmitter.php b/docker/overlays/nextcloud/html/lib/private/Hooks/LegacyEmitter.php new file mode 100644 index 0000000..470c5e0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/LegacyEmitter.php @@ -0,0 +1,40 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +abstract class LegacyEmitter extends BasicEmitter { + /** + * @param string $scope + * @param string $method + * @param array $arguments + * + * @suppress PhanAccessMethodProtected + */ + protected function emit($scope, $method, array $arguments = []) { + \OC_Hook::emit($scope, $method, $arguments); + parent::emit($scope, $method, $arguments); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Hooks/PublicEmitter.php b/docker/overlays/nextcloud/html/lib/private/Hooks/PublicEmitter.php new file mode 100644 index 0000000..dbccc34 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Hooks/PublicEmitter.php @@ -0,0 +1,42 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Hooks; + +/** + * @deprecated 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service + */ +class PublicEmitter extends BasicEmitter { + /** + * @param string $scope + * @param string $method + * @param array $arguments optional + * + * @suppress PhanAccessMethodProtected + */ + public function emit($scope, $method, array $arguments = []) { + parent::emit($scope, $method, $arguments); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Http/Client/Client.php b/docker/overlays/nextcloud/html/lib/private/Http/Client/Client.php new file mode 100644 index 0000000..3517181 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Http/Client/Client.php @@ -0,0 +1,412 @@ + + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Mohammed Abdellatif + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Scott Shambarger + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Http\Client; + +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\RequestOptions; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\LocalServerException; +use OCP\ICertificateManager; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Class Client + * + * @package OC\Http + */ +class Client implements IClient { + /** @var GuzzleClient */ + private $client; + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + /** @var ICertificateManager */ + private $certificateManager; + + public function __construct( + IConfig $config, + ILogger $logger, + ICertificateManager $certificateManager, + GuzzleClient $client + ) { + $this->config = $config; + $this->logger = $logger; + $this->client = $client; + $this->certificateManager = $certificateManager; + } + + private function buildRequestOptions(array $options): array { + $proxy = $this->getProxyUri(); + + $defaults = [ + RequestOptions::VERIFY => $this->getCertBundle(), + RequestOptions::TIMEOUT => 30, + ]; + + // Only add RequestOptions::PROXY if Nextcloud is explicitly + // configured to use a proxy. This is needed in order not to override + // Guzzle default values. + if ($proxy !== null) { + $defaults[RequestOptions::PROXY] = $proxy; + } + + $options = array_merge($defaults, $options); + + if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) { + $options[RequestOptions::HEADERS]['User-Agent'] = 'Nextcloud Server Crawler'; + } + + if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) { + $options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip'; + } + + return $options; + } + + private function getCertBundle(): string { + // If the instance is not yet setup we need to use the static path as + // $this->certificateManager->getAbsoluteBundlePath() tries to instantiiate + // a view + if ($this->config->getSystemValue('installed', false) === false) { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } + + if ($this->certificateManager->listCertificates() === []) { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } + + return $this->certificateManager->getAbsoluteBundlePath(); + } + + /** + * Returns a null or an associative array specifiying the proxy URI for + * 'http' and 'https' schemes, in addition to a 'no' key value pair + * providing a list of host names that should not be proxied to. + * + * @return array|null + * + * The return array looks like: + * [ + * 'http' => 'username:password@proxy.example.com', + * 'https' => 'username:password@proxy.example.com', + * 'no' => ['foo.com', 'bar.com'] + * ] + * + */ + private function getProxyUri(): ?array { + $proxyHost = $this->config->getSystemValue('proxy', ''); + + if ($proxyHost === '' || $proxyHost === null) { + return null; + } + + $proxyUserPwd = $this->config->getSystemValue('proxyuserpwd', ''); + if ($proxyUserPwd !== '' && $proxyUserPwd !== null) { + $proxyHost = $proxyUserPwd . '@' . $proxyHost; + } + + $proxy = [ + 'http' => $proxyHost, + 'https' => $proxyHost, + ]; + + $proxyExclude = $this->config->getSystemValue('proxyexclude', []); + if ($proxyExclude !== [] && $proxyExclude !== null) { + $proxy['no'] = $proxyExclude; + } + + return $proxy; + } + + protected function preventLocalAddress(string $uri, array $options): void { + if (($options['nextcloud']['allow_local_address'] ?? false) || + $this->config->getSystemValueBool('allow_local_remote_servers', false)) { + return; + } + + $host = parse_url($uri, PHP_URL_HOST); + if ($host === false || $host === null) { + $this->logger->warning("Could not detect any host in $uri"); + throw new LocalServerException('Could not detect any host'); + } + + $host = strtolower($host); + // remove brackets from IPv6 addresses + if (strpos($host, '[') === 0 && substr($host, -1) === ']') { + $host = substr($host, 1, -1); + } + + // Disallow localhost and local network + if ($host === 'localhost' || substr($host, -6) === '.local' || substr($host, -10) === '.localhost') { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + + // Disallow hostname only + if (substr_count($host, '.') === 0) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + + if ((bool)filter_var($host, FILTER_VALIDATE_IP) && !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + + // Also check for IPv6 IPv4 nesting, because that's not covered by filter_var + if ((bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && substr_count($host, '.') > 0) { + $delimiter = strrpos($host, ':'); // Get last colon + $ipv4Address = substr($host, $delimiter + 1); + + if (!filter_var($ipv4Address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + } + } + + /** + * Sends a GET request + * + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function get(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('get', $uri, $this->buildRequestOptions($options)); + $isStream = isset($options['stream']) && $options['stream']; + return new Response($response, $isStream); + } + + /** + * Sends a HEAD request + * + * @param string $uri + * @param array $options Array such as + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function head(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('head', $uri, $this->buildRequestOptions($options)); + return new Response($response); + } + + /** + * Sends a POST request + * + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function post(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + + if (isset($options['body']) && is_array($options['body'])) { + $options['form_params'] = $options['body']; + unset($options['body']); + } + $response = $this->client->request('post', $uri, $this->buildRequestOptions($options)); + return new Response($response); + } + + /** + * Sends a PUT request + * + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function put(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('put', $uri, $this->buildRequestOptions($options)); + return new Response($response); + } + + /** + * Sends a DELETE request + * + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function delete(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options)); + return new Response($response); + } + + /** + * Sends a options request + * + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [' + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'save_to' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function options(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('options', $uri, $this->buildRequestOptions($options)); + return new Response($response); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Http/Client/ClientService.php b/docker/overlays/nextcloud/html/lib/private/Http/Client/ClientService.php new file mode 100644 index 0000000..3858032 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Http/Client/ClientService.php @@ -0,0 +1,64 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Http\Client; + +use GuzzleHttp\Client as GuzzleClient; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\ICertificateManager; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Class ClientService + * + * @package OC\Http + */ +class ClientService implements IClientService { + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + /** @var ICertificateManager */ + private $certificateManager; + + public function __construct(IConfig $config, + ILogger $logger, + ICertificateManager $certificateManager) { + $this->config = $config; + $this->logger = $logger; + $this->certificateManager = $certificateManager; + } + + /** + * @return Client + */ + public function newClient(): IClient { + return new Client($this->config, $this->logger, $this->certificateManager, new GuzzleClient()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Http/Client/Response.php b/docker/overlays/nextcloud/html/lib/private/Http/Client/Response.php new file mode 100644 index 0000000..722fc40 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Http/Client/Response.php @@ -0,0 +1,92 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Http\Client; + +use OCP\Http\Client\IResponse; +use Psr\Http\Message\ResponseInterface; + +/** + * Class Response + * + * @package OC\Http + */ +class Response implements IResponse { + /** @var ResponseInterface */ + private $response; + + /** + * @var bool + */ + private $stream; + + /** + * @param ResponseInterface $response + * @param bool $stream + */ + public function __construct(ResponseInterface $response, $stream = false) { + $this->response = $response; + $this->stream = $stream; + } + + /** + * @return string|resource + */ + public function getBody() { + return $this->stream ? + $this->response->getBody()->detach(): + $this->response->getBody()->getContents(); + } + + /** + * @return int + */ + public function getStatusCode(): int { + return $this->response->getStatusCode(); + } + + /** + * @param string $key + * @return string + */ + public function getHeader(string $key): string { + $headers = $this->response->getHeader($key); + + if (count($headers) === 0) { + return ''; + } + + return $headers[0]; + } + + /** + * @return array + */ + public function getHeaders(): array { + return $this->response->getHeaders(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Http/CookieHelper.php b/docker/overlays/nextcloud/html/lib/private/Http/CookieHelper.php new file mode 100644 index 0000000..f7b871c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Http/CookieHelper.php @@ -0,0 +1,77 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Http; + +class CookieHelper { + public const SAMESITE_NONE = 0; + public const SAMESITE_LAX = 1; + public const SAMESITE_STRICT = 2; + + public static function setCookie(string $name, + string $value = '', + int $maxAge = 0, + string $path = '', + string $domain = '', + bool $secure = false, + bool $httponly = false, + int $samesite = self::SAMESITE_NONE) { + $header = sprintf( + 'Set-Cookie: %s=%s', + $name, + urlencode($value) + ); + + if ($path !== '') { + $header .= sprintf('; Path=%s', $path); + } + + if ($domain !== '') { + $header .= sprintf('; Domain=%s', $domain); + } + + if ($maxAge > 0) { + $header .= sprintf('; Max-Age=%d', $maxAge); + } + + if ($secure) { + $header .= '; Secure'; + } + + if ($httponly) { + $header .= '; HttpOnly'; + } + + if ($samesite === self::SAMESITE_LAX) { + $header .= '; SameSite=Lax'; + } elseif ($samesite === self::SAMESITE_STRICT) { + $header .= '; SameSite=Strict'; + } + + header($header, false); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/InitialStateService.php b/docker/overlays/nextcloud/html/lib/private/InitialStateService.php new file mode 100644 index 0000000..c74eb68 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/InitialStateService.php @@ -0,0 +1,102 @@ + + * + * @author Christoph Wurst + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC; + +use Closure; +use OCP\IInitialStateService; +use OCP\ILogger; + +class InitialStateService implements IInitialStateService { + + /** @var ILogger */ + private $logger; + + /** @var string[][] */ + private $states = []; + + /** @var Closure[][] */ + private $lazyStates = []; + + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + public function provideInitialState(string $appName, string $key, $data): void { + // Scalars and JsonSerializable are fine + if (is_scalar($data) || $data instanceof \JsonSerializable || is_array($data)) { + if (!isset($this->states[$appName])) { + $this->states[$appName] = []; + } + $this->states[$appName][$key] = json_encode($data); + return; + } + + $this->logger->warning('Invalid data provided to provideInitialState by ' . $appName); + } + + public function provideLazyInitialState(string $appName, string $key, Closure $closure): void { + if (!isset($this->lazyStates[$appName])) { + $this->lazyStates[$appName] = []; + } + $this->lazyStates[$appName][$key] = $closure; + } + + /** + * Invoke all callbacks to populate the `states` property + */ + private function invokeLazyStateCallbacks(): void { + foreach ($this->lazyStates as $app => $lazyStates) { + foreach ($lazyStates as $key => $lazyState) { + $startTime = microtime(true); + $this->provideInitialState($app, $key, $lazyState()); + $endTime = microtime(true); + $duration = $endTime - $startTime; + if ($duration > 1) { + $this->logger->warning('Lazy initial state provider for {key} took {duration} seconds.', [ + 'app' => $app, + 'key' => $key, + 'duration' => round($duration, 2), + ]); + } + } + } + $this->lazyStates = []; + } + + public function getInitialStates(): array { + $this->invokeLazyStateCallbacks(); + + $appStates = []; + foreach ($this->states as $app => $states) { + foreach ($states as $key => $value) { + $appStates["$app-$key"] = $value; + } + } + return $appStates; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Installer.php b/docker/overlays/nextcloud/html/lib/private/Installer.php new file mode 100644 index 0000000..9be79ac --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Installer.php @@ -0,0 +1,632 @@ + + * + * @author Arthur Schiwon + * @author Brice Maron + * @author Christoph Wurst + * @author Frank Karlitschek + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Kamil Domanski + * @author Lukas Reschke + * @author Markus Staab + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author root "root@oc.(none)" + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use Doctrine\DBAL\Exception\TableExistsException; +use OC\App\AppStore\Bundles\Bundle; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\Archive\TAR; +use OC_App; +use OC_DB; +use OC_Helper; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; +use OCP\ITempManager; +use phpseclib\File\X509; + +/** + * This class provides the functionality needed to install, update and remove apps + */ +class Installer { + /** @var AppFetcher */ + private $appFetcher; + /** @var IClientService */ + private $clientService; + /** @var ITempManager */ + private $tempManager; + /** @var ILogger */ + private $logger; + /** @var IConfig */ + private $config; + /** @var array - for caching the result of app fetcher */ + private $apps = null; + /** @var bool|null - for caching the result of the ready status */ + private $isInstanceReadyForUpdates = null; + /** @var bool */ + private $isCLI; + + /** + * @param AppFetcher $appFetcher + * @param IClientService $clientService + * @param ITempManager $tempManager + * @param ILogger $logger + * @param IConfig $config + */ + public function __construct( + AppFetcher $appFetcher, + IClientService $clientService, + ITempManager $tempManager, + ILogger $logger, + IConfig $config, + bool $isCLI + ) { + $this->appFetcher = $appFetcher; + $this->clientService = $clientService; + $this->tempManager = $tempManager; + $this->logger = $logger; + $this->config = $config; + $this->isCLI = $isCLI; + } + + /** + * Installs an app that is located in one of the app folders already + * + * @param string $appId App to install + * @param bool $forceEnable + * @throws \Exception + * @return string app ID + */ + public function installApp(string $appId, bool $forceEnable = false): string { + $app = \OC_App::findAppInDirectories($appId); + if ($app === false) { + throw new \Exception('App not found in any app directory'); + } + + $basedir = $app['path'].'/'.$appId; + $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true); + + $l = \OC::$server->getL10N('core'); + + if (!is_array($info)) { + throw new \Exception( + $l->t('App "%s" cannot be installed because appinfo file cannot be read.', + [$appId] + ) + ); + } + + $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []); + $ignoreMax = $forceEnable || in_array($appId, $ignoreMaxApps, true); + + $version = implode('.', \OCP\Util::getVersion()); + if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) { + throw new \Exception( + // TODO $l + $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.', + [$info['name']] + ) + ); + } + + // check for required dependencies + \OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax); + \OC_App::registerAutoloading($appId, $basedir); + + $previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false); + if ($previousVersion) { + OC_App::executeRepairSteps($appId, $info['repair-steps']['pre-migration']); + } + + //install the database + if (is_file($basedir.'/appinfo/database.xml')) { + if (\OC::$server->getConfig()->getAppValue($info['id'], 'installed_version') === null) { + OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + } else { + OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); + } + } else { + $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection()); + $ms->migrate(); + } + if ($previousVersion) { + OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']); + } + + \OC_App::setupBackgroundJobs($info['background-jobs']); + + //run appinfo/install.php + self::includeAppScript($basedir . '/appinfo/install.php'); + + $appData = OC_App::getAppInfo($appId); + OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']); + + //set the installed version + \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'], false)); + \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no'); + + //set remote/public handlers + foreach ($info['remote'] as $name=>$path) { + \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); + } + foreach ($info['public'] as $name=>$path) { + \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); + } + + OC_App::setAppTypes($info['id']); + + return $info['id']; + } + + /** + * Updates the specified app from the appstore + * + * @param string $appId + * @param bool [$allowUnstable] Allow unstable releases + * @return bool + */ + public function updateAppstoreApp($appId, $allowUnstable = false) { + if ($this->isUpdateAvailable($appId, $allowUnstable)) { + try { + $this->downloadApp($appId, $allowUnstable); + } catch (\Exception $e) { + $this->logger->logException($e, [ + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + return OC_App::updateApp($appId); + } + + return false; + } + + /** + * Downloads an app and puts it into the app directory + * + * @param string $appId + * @param bool [$allowUnstable] + * + * @throws \Exception If the installation was not successful + */ + public function downloadApp($appId, $allowUnstable = false) { + $appId = strtolower($appId); + + $apps = $this->appFetcher->get($allowUnstable); + foreach ($apps as $app) { + if ($app['id'] === $appId) { + // Load the certificate + $certificate = new X509(); + $certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt')); + $loadedCertificate = $certificate->loadX509($app['certificate']); + + // Verify if the certificate has been revoked + $crl = new X509(); + $crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt')); + $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl')); + if ($crl->validateSignature() !== true) { + throw new \Exception('Could not validate CRL signature'); + } + $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString(); + $revoked = $crl->getRevoked($csn); + if ($revoked !== false) { + throw new \Exception( + sprintf( + 'Certificate "%s" has been revoked', + $csn + ) + ); + } + + // Verify if the certificate has been issued by the Nextcloud Code Authority CA + if ($certificate->validateSignature() !== true) { + throw new \Exception( + sprintf( + 'App with id %s has a certificate not issued by a trusted Code Signing Authority', + $appId + ) + ); + } + + // Verify if the certificate is issued for the requested app id + $certInfo = openssl_x509_parse($app['certificate']); + if (!isset($certInfo['subject']['CN'])) { + throw new \Exception( + sprintf( + 'App with id %s has a cert with no CN', + $appId + ) + ); + } + if ($certInfo['subject']['CN'] !== $appId) { + throw new \Exception( + sprintf( + 'App with id %s has a cert issued to %s', + $appId, + $certInfo['subject']['CN'] + ) + ); + } + + // Download the release + $tempFile = $this->tempManager->getTemporaryFile('.tar.gz'); + $timeout = $this->isCLI ? 0 : 120; + $client = $this->clientService->newClient(); + $client->get($app['releases'][0]['download'], ['save_to' => $tempFile, 'timeout' => $timeout]); + + // Check if the signature actually matches the downloaded content + $certificate = openssl_get_publickey($app['certificate']); + $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512); + openssl_free_key($certificate); + + if ($verified === true) { + // Seems to match, let's proceed + $extractDir = $this->tempManager->getTemporaryFolder(); + $archive = new TAR($tempFile); + + if ($archive) { + if (!$archive->extract($extractDir)) { + throw new \Exception( + sprintf( + 'Could not extract app %s', + $appId + ) + ); + } + $allFiles = scandir($extractDir); + $folders = array_diff($allFiles, ['.', '..']); + $folders = array_values($folders); + + if (count($folders) > 1) { + throw new \Exception( + sprintf( + 'Extracted app %s has more than 1 folder', + $appId + ) + ); + } + + // Check if appinfo/info.xml has the same app ID as well + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml'); + libxml_disable_entity_loader($loadEntities); + if ((string)$xml->id !== $appId) { + throw new \Exception( + sprintf( + 'App for id %s has a wrong app ID in info.xml: %s', + $appId, + (string)$xml->id + ) + ); + } + + // Check if the version is lower than before + $currentVersion = OC_App::getAppVersion($appId); + $newVersion = (string)$xml->version; + if (version_compare($currentVersion, $newVersion) === 1) { + throw new \Exception( + sprintf( + 'App for id %s has version %s and tried to update to lower version %s', + $appId, + $currentVersion, + $newVersion + ) + ); + } + + $baseDir = OC_App::getInstallPath() . '/' . $appId; + // Remove old app with the ID if existent + OC_Helper::rmdirr($baseDir); + // Move to app folder + if (@mkdir($baseDir)) { + $extractDir .= '/' . $folders[0]; + OC_Helper::copyr($extractDir, $baseDir); + } + OC_Helper::copyr($extractDir, $baseDir); + OC_Helper::rmdirr($extractDir); + return; + } else { + throw new \Exception( + sprintf( + 'Could not extract app with ID %s to %s', + $appId, + $extractDir + ) + ); + } + } else { + // Signature does not match + throw new \Exception( + sprintf( + 'App with id %s has invalid signature', + $appId + ) + ); + } + } + } + + throw new \Exception( + sprintf( + 'Could not download app %s', + $appId + ) + ); + } + + /** + * Check if an update for the app is available + * + * @param string $appId + * @param bool $allowUnstable + * @return string|false false or the version number of the update + */ + public function isUpdateAvailable($appId, $allowUnstable = false) { + if ($this->isInstanceReadyForUpdates === null) { + $installPath = OC_App::getInstallPath(); + if ($installPath === false || $installPath === null) { + $this->isInstanceReadyForUpdates = false; + } else { + $this->isInstanceReadyForUpdates = true; + } + } + + if ($this->isInstanceReadyForUpdates === false) { + return false; + } + + if ($this->isInstalledFromGit($appId) === true) { + return false; + } + + if ($this->apps === null) { + $this->apps = $this->appFetcher->get($allowUnstable); + } + + foreach ($this->apps as $app) { + if ($app['id'] === $appId) { + $currentVersion = OC_App::getAppVersion($appId); + + if (!isset($app['releases'][0]['version'])) { + return false; + } + $newestVersion = $app['releases'][0]['version']; + if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) { + return $newestVersion; + } else { + return false; + } + } + } + + return false; + } + + /** + * Check if app has been installed from git + * @param string $name name of the application to remove + * @return boolean + * + * The function will check if the path contains a .git folder + */ + private function isInstalledFromGit($appId) { + $app = \OC_App::findAppInDirectories($appId); + if ($app === false) { + return false; + } + $basedir = $app['path'].'/'.$appId; + return file_exists($basedir.'/.git/'); + } + + /** + * Check if app is already downloaded + * @param string $name name of the application to remove + * @return boolean + * + * The function will check if the app is already downloaded in the apps repository + */ + public function isDownloaded($name) { + foreach (\OC::$APPSROOTS as $dir) { + $dirToTest = $dir['path']; + $dirToTest .= '/'; + $dirToTest .= $name; + $dirToTest .= '/'; + + if (is_dir($dirToTest)) { + return true; + } + } + + return false; + } + + /** + * Removes an app + * @param string $appId ID of the application to remove + * @return boolean + * + * + * This function works as follows + * -# call uninstall repair steps + * -# removing the files + * + * The function will not delete preferences, tables and the configuration, + * this has to be done by the function oc_app_uninstall(). + */ + public function removeApp($appId) { + if ($this->isDownloaded($appId)) { + if (\OC::$server->getAppManager()->isShipped($appId)) { + return false; + } + $appDir = OC_App::getInstallPath() . '/' . $appId; + OC_Helper::rmdirr($appDir); + return true; + } else { + \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', ILogger::ERROR); + + return false; + } + } + + /** + * Installs the app within the bundle and marks the bundle as installed + * + * @param Bundle $bundle + * @throws \Exception If app could not get installed + */ + public function installAppBundle(Bundle $bundle) { + $appIds = $bundle->getAppIdentifiers(); + foreach ($appIds as $appId) { + if (!$this->isDownloaded($appId)) { + $this->downloadApp($appId); + } + $this->installApp($appId); + $app = new OC_App(); + $app->enable($appId); + } + $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true); + $bundles[] = $bundle->getIdentifier(); + $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles)); + } + + /** + * Installs shipped apps + * + * This function installs all apps found in the 'apps' directory that should be enabled by default; + * @param bool $softErrors When updating we ignore errors and simply log them, better to have a + * working ownCloud at the end instead of an aborted update. + * @return array Array of error messages (appid => Exception) + */ + public static function installShippedApps($softErrors = false) { + $appManager = \OC::$server->getAppManager(); + $config = \OC::$server->getConfig(); + $errors = []; + foreach (\OC::$APPSROOTS as $app_dir) { + if ($dir = opendir($app_dir['path'])) { + while (false !== ($filename = readdir($dir))) { + if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) { + if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) { + if ($config->getAppValue($filename, "installed_version", null) === null) { + $info=OC_App::getAppInfo($filename); + $enabled = isset($info['default_enable']); + if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps())) + && $config->getAppValue($filename, 'enabled') !== 'no') { + if ($softErrors) { + try { + Installer::installShippedApp($filename); + } catch (HintException $e) { + if ($e->getPrevious() instanceof TableExistsException) { + $errors[$filename] = $e; + continue; + } + throw $e; + } + } else { + Installer::installShippedApp($filename); + } + $config->setAppValue($filename, 'enabled', 'yes'); + } + } + } + } + } + closedir($dir); + } + } + + return $errors; + } + + /** + * install an app already placed in the app folder + * @param string $app id of the app to install + * @return integer + */ + public static function installShippedApp($app) { + //install the database + $appPath = OC_App::getAppPath($app); + \OC_App::registerAutoloading($app, $appPath); + + if (is_file("$appPath/appinfo/database.xml")) { + try { + OC_DB::createDbFromStructure("$appPath/appinfo/database.xml"); + } catch (TableExistsException $e) { + throw new HintException( + 'Failed to enable app ' . $app, + 'Please ask for help via one of our support channels.', + 0, $e + ); + } + } else { + $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection()); + $ms->migrate(); + } + + //run appinfo/install.php + self::includeAppScript("$appPath/appinfo/install.php"); + + $info = OC_App::getAppInfo($app); + if (is_null($info)) { + return false; + } + \OC_App::setupBackgroundJobs($info['background-jobs']); + + OC_App::executeRepairSteps($app, $info['repair-steps']['install']); + + $config = \OC::$server->getConfig(); + + $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app)); + if (array_key_exists('ocsid', $info)) { + $config->setAppValue($app, 'ocsid', $info['ocsid']); + } + + //set remote/public handlers + foreach ($info['remote'] as $name=>$path) { + $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path); + } + foreach ($info['public'] as $name=>$path) { + $config->setAppValue('core', 'public_'.$name, $app.'/'.$path); + } + + OC_App::setAppTypes($info['id']); + + return $info['id']; + } + + /** + * @param string $script + */ + private static function includeAppScript($script) { + if (file_exists($script)) { + include $script; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Checker.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Checker.php new file mode 100644 index 0000000..f5c4d6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Checker.php @@ -0,0 +1,597 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Victor Dubiniuk + * @author Vincent Petry + * @author Xheni Myrtaj + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck; + +use OC\Core\Command\Maintenance\Mimetype\GenerateMimetypeFileBuilder; +use OC\IntegrityCheck\Exceptions\InvalidSignatureException; +use OC\IntegrityCheck\Helpers\AppLocator; +use OC\IntegrityCheck\Helpers\EnvironmentHelper; +use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OC\IntegrityCheck\Iterator\ExcludeFileByNameFilterIterator; +use OC\IntegrityCheck\Iterator\ExcludeFoldersByPathFilterIterator; +use OCP\App\IAppManager; +use OCP\Files\IMimeTypeDetector; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\ITempManager; +use phpseclib\Crypt\RSA; +use phpseclib\File\X509; + +/** + * Class Checker handles the code signing using X.509 and RSA. ownCloud ships with + * a public root certificate certificate that allows to issue new certificates that + * will be trusted for signing code. The CN will be used to verify that a certificate + * given to a third-party developer may not be used for other applications. For + * example the author of the application "calendar" would only receive a certificate + * only valid for this application. + * + * @package OC\IntegrityCheck + */ +class Checker { + public const CACHE_KEY = 'oc.integritycheck.checker'; + /** @var EnvironmentHelper */ + private $environmentHelper; + /** @var AppLocator */ + private $appLocator; + /** @var FileAccessHelper */ + private $fileAccessHelper; + /** @var IConfig */ + private $config; + /** @var ICache */ + private $cache; + /** @var IAppManager */ + private $appManager; + /** @var ITempManager */ + private $tempManager; + /** @var IMimeTypeDetector */ + private $mimeTypeDetector; + + /** + * @param EnvironmentHelper $environmentHelper + * @param FileAccessHelper $fileAccessHelper + * @param AppLocator $appLocator + * @param IConfig $config + * @param ICacheFactory $cacheFactory + * @param IAppManager $appManager + * @param ITempManager $tempManager + * @param IMimeTypeDetector $mimeTypeDetector + */ + public function __construct(EnvironmentHelper $environmentHelper, + FileAccessHelper $fileAccessHelper, + AppLocator $appLocator, + IConfig $config = null, + ICacheFactory $cacheFactory, + IAppManager $appManager = null, + ITempManager $tempManager, + IMimeTypeDetector $mimeTypeDetector) { + $this->environmentHelper = $environmentHelper; + $this->fileAccessHelper = $fileAccessHelper; + $this->appLocator = $appLocator; + $this->config = $config; + $this->cache = $cacheFactory->createDistributed(self::CACHE_KEY); + $this->appManager = $appManager; + $this->tempManager = $tempManager; + $this->mimeTypeDetector = $mimeTypeDetector; + } + + /** + * Whether code signing is enforced or not. + * + * @return bool + */ + public function isCodeCheckEnforced(): bool { + $notSignedChannels = [ '', 'git']; + if (\in_array($this->environmentHelper->getChannel(), $notSignedChannels, true)) { + return false; + } + + /** + * This config option is undocumented and supposed to be so, it's only + * applicable for very specific scenarios and we should not advertise it + * too prominent. So please do not add it to config.sample.php. + */ + $isIntegrityCheckDisabled = false; + if ($this->config !== null) { + $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false); + } + if ($isIntegrityCheckDisabled === true) { + return false; + } + + return true; + } + + /** + * Enumerates all files belonging to the folder. Sensible defaults are excluded. + * + * @param string $folderToIterate + * @param string $root + * @return \RecursiveIteratorIterator + * @throws \Exception + */ + private function getFolderIterator(string $folderToIterate, string $root = ''): \RecursiveIteratorIterator { + $dirItr = new \RecursiveDirectoryIterator( + $folderToIterate, + \RecursiveDirectoryIterator::SKIP_DOTS + ); + if ($root === '') { + $root = \OC::$SERVERROOT; + } + $root = rtrim($root, '/'); + + $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); + $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator, $root); + + return new \RecursiveIteratorIterator( + $excludeFoldersIterator, + \RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found + * in the iterator. + * + * @param \RecursiveIteratorIterator $iterator + * @param string $path + * @return array Array of hashes. + */ + private function generateHashes(\RecursiveIteratorIterator $iterator, + string $path): array { + $hashes = []; + + $baseDirectoryLength = \strlen($path); + foreach ($iterator as $filename => $data) { + /** @var \DirectoryIterator $data */ + if ($data->isDir()) { + continue; + } + + $relativeFileName = substr($filename, $baseDirectoryLength); + $relativeFileName = ltrim($relativeFileName, '/'); + + // Exclude signature.json files in the appinfo and root folder + if ($relativeFileName === 'appinfo/signature.json') { + continue; + } + // Exclude signature.json files in the appinfo and core folder + if ($relativeFileName === 'core/signature.json') { + continue; + } + + // The .htaccess file in the root folder of ownCloud can contain + // custom content after the installation due to the fact that dynamic + // content is written into it at installation time as well. This + // includes for example the 404 and 403 instructions. + // Thus we ignore everything below the first occurrence of + // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the + // hash generated based on this. + if ($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { + $fileContent = file_get_contents($filename); + $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); + if (\count($explodedArray) === 2) { + $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); + continue; + } + } + if ($filename === $this->environmentHelper->getServerRoot() . '/core/js/mimetypelist.js') { + $oldMimetypeList = new GenerateMimetypeFileBuilder(); + $newFile = $oldMimetypeList->generateFile($this->mimeTypeDetector->getAllAliases()); + if ($newFile === file_get_contents($filename)) { + $hashes[$relativeFileName] = hash('sha512', $oldMimetypeList->generateFile($this->mimeTypeDetector->getOnlyDefaultAliases())); + continue; + } + } + + $hashes[$relativeFileName] = hash_file('sha512', $filename); + } + + return $hashes; + } + + /** + * Creates the signature data + * + * @param array $hashes + * @param X509 $certificate + * @param RSA $privateKey + * @return array + */ + private function createSignatureData(array $hashes, + X509 $certificate, + RSA $privateKey): array { + ksort($hashes); + + $privateKey->setSignatureMode(RSA::SIGNATURE_PSS); + $privateKey->setMGFHash('sha512'); + // See https://tools.ietf.org/html/rfc3447#page-38 + $privateKey->setSaltLength(0); + $signature = $privateKey->sign(json_encode($hashes)); + + return [ + 'hashes' => $hashes, + 'signature' => base64_encode($signature), + 'certificate' => $certificate->saveX509($certificate->currentCert), + ]; + } + + /** + * Write the signature of the app in the specified folder + * + * @param string $path + * @param X509 $certificate + * @param RSA $privateKey + * @throws \Exception + */ + public function writeAppSignature($path, + X509 $certificate, + RSA $privateKey) { + $appInfoDir = $path . '/appinfo'; + try { + $this->fileAccessHelper->assertDirectoryExists($appInfoDir); + + $iterator = $this->getFolderIterator($path); + $hashes = $this->generateHashes($iterator, $path); + $signature = $this->createSignatureData($hashes, $certificate, $privateKey); + $this->fileAccessHelper->file_put_contents( + $appInfoDir . '/signature.json', + json_encode($signature, JSON_PRETTY_PRINT) + ); + } catch (\Exception $e) { + if (!$this->fileAccessHelper->is_writable($appInfoDir)) { + throw new \Exception($appInfoDir . ' is not writable'); + } + throw $e; + } + } + + /** + * Write the signature of core + * + * @param X509 $certificate + * @param RSA $rsa + * @param string $path + * @throws \Exception + */ + public function writeCoreSignature(X509 $certificate, + RSA $rsa, + $path) { + $coreDir = $path . '/core'; + try { + $this->fileAccessHelper->assertDirectoryExists($coreDir); + $iterator = $this->getFolderIterator($path, $path); + $hashes = $this->generateHashes($iterator, $path); + $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); + $this->fileAccessHelper->file_put_contents( + $coreDir . '/signature.json', + json_encode($signatureData, JSON_PRETTY_PRINT) + ); + } catch (\Exception $e) { + if (!$this->fileAccessHelper->is_writable($coreDir)) { + throw new \Exception($coreDir . ' is not writable'); + } + throw $e; + } + } + + /** + * Verifies the signature for the specified path. + * + * @param string $signaturePath + * @param string $basePath + * @param string $certificateCN + * @return array + * @throws InvalidSignatureException + * @throws \Exception + */ + private function verify(string $signaturePath, string $basePath, string $certificateCN): array { + if (!$this->isCodeCheckEnforced()) { + return []; + } + + $content = $this->fileAccessHelper->file_get_contents($signaturePath); + $signatureData = null; + + if (\is_string($content)) { + $signatureData = json_decode($content, true); + } + if (!\is_array($signatureData)) { + throw new InvalidSignatureException('Signature data not found.'); + } + + $expectedHashes = $signatureData['hashes']; + ksort($expectedHashes); + $signature = base64_decode($signatureData['signature']); + $certificate = $signatureData['certificate']; + + // Check if certificate is signed by Nextcloud Root Authority + $x509 = new \phpseclib\File\X509(); + $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot().'/resources/codesigning/root.crt'); + $x509->loadCA($rootCertificatePublicKey); + $x509->loadX509($certificate); + if (!$x509->validateSignature()) { + throw new InvalidSignatureException('Certificate is not valid.'); + } + // Verify if certificate has proper CN. "core" CN is always trusted. + if ($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') { + throw new InvalidSignatureException( + sprintf('Certificate is not valid for required scope. (Requested: %s, current: CN=%s)', $certificateCN, $x509->getDN(true)['CN']) + ); + } + + // Check if the signature of the files is valid + $rsa = new \phpseclib\Crypt\RSA(); + $rsa->loadKey($x509->currentCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']); + $rsa->setSignatureMode(RSA::SIGNATURE_PSS); + $rsa->setMGFHash('sha512'); + // See https://tools.ietf.org/html/rfc3447#page-38 + $rsa->setSaltLength(0); + if (!$rsa->verify(json_encode($expectedHashes), $signature)) { + throw new InvalidSignatureException('Signature could not get verified.'); + } + + // Fixes for the updater as shipped with ownCloud 9.0.x: The updater is + // replaced after the code integrity check is performed. + // + // Due to this reason we exclude the whole updater/ folder from the code + // integrity check. + if ($basePath === $this->environmentHelper->getServerRoot()) { + foreach ($expectedHashes as $fileName => $hash) { + if (strpos($fileName, 'updater/') === 0) { + unset($expectedHashes[$fileName]); + } + } + } + + // Compare the list of files which are not identical + $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($basePath), $basePath); + $differencesA = array_diff($expectedHashes, $currentInstanceHashes); + $differencesB = array_diff($currentInstanceHashes, $expectedHashes); + $differences = array_unique(array_merge($differencesA, $differencesB)); + $differenceArray = []; + foreach ($differences as $filename => $hash) { + // Check if file should not exist in the new signature table + if (!array_key_exists($filename, $expectedHashes)) { + $differenceArray['EXTRA_FILE'][$filename]['expected'] = ''; + $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash; + continue; + } + + // Check if file is missing + if (!array_key_exists($filename, $currentInstanceHashes)) { + $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes[$filename]; + $differenceArray['FILE_MISSING'][$filename]['current'] = ''; + continue; + } + + // Check if hash does mismatch + if ($expectedHashes[$filename] !== $currentInstanceHashes[$filename]) { + $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes[$filename]; + $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename]; + continue; + } + + // Should never happen. + throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.'); + } + + return $differenceArray; + } + + /** + * Whether the code integrity check has passed successful or not + * + * @return bool + */ + public function hasPassedCheck(): bool { + $results = $this->getResults(); + if (empty($results)) { + return true; + } + + return false; + } + + /** + * @return array + */ + public function getResults(): array { + $cachedResults = $this->cache->get(self::CACHE_KEY); + if (!\is_null($cachedResults)) { + return json_decode($cachedResults, true); + } + + if ($this->config !== null) { + return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true); + } + return []; + } + + /** + * Stores the results in the app config as well as cache + * + * @param string $scope + * @param array $result + */ + private function storeResults(string $scope, array $result) { + $resultArray = $this->getResults(); + unset($resultArray[$scope]); + if (!empty($result)) { + $resultArray[$scope] = $result; + } + if ($this->config !== null) { + $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray)); + } + $this->cache->set(self::CACHE_KEY, json_encode($resultArray)); + } + + /** + * + * Clean previous results for a proper rescanning. Otherwise + */ + private function cleanResults() { + $this->config->deleteAppValue('core', self::CACHE_KEY); + $this->cache->remove(self::CACHE_KEY); + } + + /** + * Verify the signature of $appId. Returns an array with the following content: + * [ + * 'FILE_MISSING' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'EXTRA_FILE' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'INVALID_HASH' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * ] + * + * Array may be empty in case no problems have been found. + * + * @param string $appId + * @param string $path Optional path. If none is given it will be guessed. + * @return array + */ + public function verifyAppSignature(string $appId, string $path = ''): array { + try { + if ($path === '') { + $path = $this->appLocator->getAppPath($appId); + } + $result = $this->verify( + $path . '/appinfo/signature.json', + $path, + $appId + ); + } catch (\Exception $e) { + $result = [ + 'EXCEPTION' => [ + 'class' => \get_class($e), + 'message' => $e->getMessage(), + ], + ]; + } + $this->storeResults($appId, $result); + + return $result; + } + + /** + * Verify the signature of core. Returns an array with the following content: + * [ + * 'FILE_MISSING' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'EXTRA_FILE' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'INVALID_HASH' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * ] + * + * Array may be empty in case no problems have been found. + * + * @return array + */ + public function verifyCoreSignature(): array { + try { + $result = $this->verify( + $this->environmentHelper->getServerRoot() . '/core/signature.json', + $this->environmentHelper->getServerRoot(), + 'core' + ); + } catch (\Exception $e) { + $result = [ + 'EXCEPTION' => [ + 'class' => \get_class($e), + 'message' => $e->getMessage(), + ], + ]; + } + $this->storeResults('core', $result); + + return $result; + } + + /** + * Verify the core code of the instance as well as all applicable applications + * and store the results. + */ + public function runInstanceVerification() { + $this->cleanResults(); + $this->verifyCoreSignature(); + $appIds = $this->appLocator->getAllApps(); + foreach ($appIds as $appId) { + // If an application is shipped a valid signature is required + $isShipped = $this->appManager->isShipped($appId); + $appNeedsToBeChecked = false; + if ($isShipped) { + $appNeedsToBeChecked = true; + } elseif ($this->fileAccessHelper->file_exists($this->appLocator->getAppPath($appId) . '/appinfo/signature.json')) { + // Otherwise only if the application explicitly ships a signature.json file + $appNeedsToBeChecked = true; + } + + if ($appNeedsToBeChecked) { + $this->verifyAppSignature($appId); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php new file mode 100644 index 0000000..daef60a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php @@ -0,0 +1,33 @@ + + * @author Lukas Reschke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Exceptions; + +/** + * Class InvalidSignatureException is thrown in case the signature of the hashes + * cannot be properly validated. This indicates that either files + * + * @package OC\IntegrityCheck\Exceptions + */ +class InvalidSignatureException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/AppLocator.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/AppLocator.php new file mode 100644 index 0000000..fdfc81d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/AppLocator.php @@ -0,0 +1,61 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Helpers; + +/** + * Class AppLocator provides a non-static helper for OC_App::getPath($appId) + * it is not possible to use IAppManager at this point as IAppManager has a + * dependency on a running ownCloud. + * + * @package OC\IntegrityCheck\Helpers + */ +class AppLocator { + /** + * Provides \OC_App::getAppPath($appId) + * + * @param string $appId + * @return string + * @throws \Exception If the app cannot be found + */ + public function getAppPath(string $appId): string { + $path = \OC_App::getAppPath($appId); + if ($path === false) { + throw new \Exception('App not found'); + } + return $path; + } + + /** + * Providers \OC_App::getAllApps() + * + * @return array + */ + public function getAllApps(): array { + return \OC_App::getAllApps(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php new file mode 100644 index 0000000..8144103 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php @@ -0,0 +1,53 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Helpers; + +/** + * Class EnvironmentHelper provides a non-static helper for access to static + * variables such as \OC::$SERVERROOT. + * + * @package OC\IntegrityCheck\Helpers + */ +class EnvironmentHelper { + /** + * Provides \OC::$SERVERROOT + * + * @return string + */ + public function getServerRoot(): string { + return rtrim(\OC::$SERVERROOT, '/'); + } + + /** + * Provides \OC_Util::getChannel() + * + * @return string + */ + public function getChannel(): string { + return \OC_Util::getChannel(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php new file mode 100644 index 0000000..2a0c0dd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php @@ -0,0 +1,91 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Helpers; + +/** + * Class FileAccessHelper provides a helper around file_get_contents and + * file_put_contents + * + * @package OC\IntegrityCheck\Helpers + */ +class FileAccessHelper { + /** + * Wrapper around file_get_contents($filename, $data) + * + * @param string $filename + * @return string|false + */ + public function file_get_contents(string $filename) { + return file_get_contents($filename); + } + + /** + * Wrapper around file_exists($filename) + * + * @param string $filename + * @return bool + */ + public function file_exists(string $filename): bool { + return file_exists($filename); + } + + /** + * Wrapper around file_put_contents($filename, $data) + * + * @param string $filename + * @param string $data + * @return int + * @throws \Exception + */ + public function file_put_contents(string $filename, string $data): int { + $bytesWritten = @file_put_contents($filename, $data); + if ($bytesWritten === false || $bytesWritten !== \strlen($data)) { + throw new \Exception('Failed to write into ' . $filename); + } + return $bytesWritten; + } + + /** + * @param string $path + * @return bool + */ + public function is_writable(string $path): bool { + return is_writable($path); + } + + /** + * @param string $path + * @throws \Exception + */ + public function assertDirectoryExists(string $path) { + if (!is_dir($path)) { + throw new \Exception('Directory ' . $path . ' does not exist.'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php new file mode 100644 index 0000000..016c244 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php @@ -0,0 +1,87 @@ + + * @author Lukas Reschke + * @author Romain Rivière + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Iterator; + +/** + * Class ExcludeFileByNameFilterIterator provides a custom iterator which excludes + * entries with the specified file name from the file list. These file names are matched exactly. + * + * @package OC\Integritycheck\Iterator + */ +class ExcludeFileByNameFilterIterator extends \RecursiveFilterIterator { + /** + * Array of excluded file names. Those are not scanned by the integrity checker. + * This is used to exclude files which administrators could upload by mistakes + * such as .DS_Store files. + * + * @var array + */ + private $excludedFilenames = [ + '.DS_Store', // Mac OS X + 'Thumbs.db', // Microsoft Windows + '.directory', // Dolphin (KDE) + '.webapp', // Gentoo/Funtoo & derivatives use a tool known as webapp-config to manage web-apps. + '.rnd', + ]; + + /** + * Array of excluded file name parts. Those are not scanned by the integrity checker. + * These strings are regular expressions and any file names + * matching these expressions are ignored. + * + * @var array + */ + private $excludedFilenamePatterns = [ + '/^\.webapp-nextcloud-(\d+\.){2}(\d+)(-r\d+)?$/', // Gentoo/Funtoo & derivatives use a tool known as webapp-config to manage wep-apps. + ]; + + /** + * @return bool + */ + public function accept() { + /** @var \SplFileInfo $current */ + $current = $this->current(); + + if ($current->isDir()) { + return true; + } + + $currentFileName = $current->getFilename(); + if (in_array($currentFileName, $this->excludedFilenames, true)) { + return false; + } + + foreach ($this->excludedFilenamePatterns as $pattern) { + if (preg_match($pattern, $currentFileName) > 0) { + return false; + } + } + + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php new file mode 100644 index 0000000..51b1340 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php @@ -0,0 +1,73 @@ + + * @author Lukas Reschke + * @author RealRancor + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\IntegrityCheck\Iterator; + +class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator { + private $excludedFolders; + + public function __construct(\RecursiveIterator $iterator, $root = '') { + parent::__construct($iterator); + + $appFolders = \OC::$APPSROOTS; + foreach ($appFolders as $key => $appFolder) { + $appFolders[$key] = rtrim($appFolder['path'], '/'); + } + + $excludedFolders = [ + rtrim($root . '/data', '/'), + rtrim($root . '/themes', '/'), + rtrim($root . '/config', '/'), + rtrim($root . '/apps', '/'), + rtrim($root . '/assets', '/'), + rtrim($root . '/lost+found', '/'), + // Ignore folders generated by updater since the updater is replaced + // after the integrity check is run. + // See https://github.com/owncloud/updater/issues/318#issuecomment-212497846 + rtrim($root . '/updater', '/'), + rtrim($root . '/_oc_upgrade', '/'), + ]; + $customDataDir = \OC::$server->getConfig()->getSystemValue('datadirectory', ''); + if ($customDataDir !== '') { + $excludedFolders[] = rtrim($customDataDir, '/'); + } + + $this->excludedFolders = array_merge($excludedFolders, $appFolders); + } + + /** + * @return bool + */ + public function accept() { + return !\in_array( + $this->current()->getPathName(), + $this->excludedFolders, + true + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/Factory.php b/docker/overlays/nextcloud/html/lib/private/L10N/Factory.php new file mode 100644 index 0000000..11f572f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/Factory.php @@ -0,0 +1,681 @@ + + * @copyright 2016 Lukas Reschke + * + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Georg Ehrke + * @author GretaD + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Citharel + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\L10N; + +use OCP\IConfig; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; +use OCP\L10N\IFactory; +use OCP\L10N\ILanguageIterator; + +/** + * A factory that generates language instances + */ +class Factory implements IFactory { + + /** @var string */ + protected $requestLanguage = ''; + + /** + * cached instances + * @var array Structure: Lang => App => \OCP\IL10N + */ + protected $instances = []; + + /** + * @var array Structure: App => string[] + */ + protected $availableLanguages = []; + + /** + * @var array + */ + protected $availableLocales = []; + + /** + * @var array Structure: string => callable + */ + protected $pluralFunctions = []; + + public const COMMON_LANGUAGE_CODES = [ + 'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it', + 'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko' + ]; + + /** @var IConfig */ + protected $config; + + /** @var IRequest */ + protected $request; + + /** @var IUserSession */ + protected $userSession; + + /** @var string */ + protected $serverRoot; + + /** + * @param IConfig $config + * @param IRequest $request + * @param IUserSession $userSession + * @param string $serverRoot + */ + public function __construct(IConfig $config, + IRequest $request, + IUserSession $userSession, + $serverRoot) { + $this->config = $config; + $this->request = $request; + $this->userSession = $userSession; + $this->serverRoot = $serverRoot; + } + + /** + * Get a language instance + * + * @param string $app + * @param string|null $lang + * @param string|null $locale + * @return \OCP\IL10N + */ + public function get($app, $lang = null, $locale = null) { + return new LazyL10N(function () use ($app, $lang, $locale) { + $app = \OC_App::cleanAppId($app); + if ($lang !== null) { + $lang = str_replace(['\0', '/', '\\', '..'], '', (string)$lang); + } + + $forceLang = $this->config->getSystemValue('force_language', false); + if (is_string($forceLang)) { + $lang = $forceLang; + } + + $forceLocale = $this->config->getSystemValue('force_locale', false); + if (is_string($forceLocale)) { + $locale = $forceLocale; + } + + if ($lang === null || !$this->languageExists($app, $lang)) { + $lang = $this->findLanguage($app); + } + + if ($locale === null || !$this->localeExists($locale)) { + $locale = $this->findLocale($lang); + } + + if (!isset($this->instances[$lang][$app])) { + $this->instances[$lang][$app] = new L10N( + $this, $app, $lang, $locale, + $this->getL10nFilesForApp($app, $lang) + ); + } + + return $this->instances[$lang][$app]; + }); + } + + /** + * Find the best language + * + * @param string|null $app App id or null for core + * @return string language If nothing works it returns 'en' + */ + public function findLanguage($app = null) { + $forceLang = $this->config->getSystemValue('force_language', false); + if (is_string($forceLang)) { + $this->requestLanguage = $forceLang; + } + + if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) { + return $this->requestLanguage; + } + + /** + * At this point Nextcloud might not yet be installed and thus the lookup + * in the preferences table might fail. For this reason we need to check + * whether the instance has already been installed + * + * @link https://github.com/owncloud/core/issues/21955 + */ + if ($this->config->getSystemValue('installed', false)) { + $userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() : null; + if (!is_null($userId)) { + $userLang = $this->config->getUserValue($userId, 'core', 'lang', null); + } else { + $userLang = null; + } + } else { + $userId = null; + $userLang = null; + } + + if ($userLang) { + $this->requestLanguage = $userLang; + if ($this->languageExists($app, $userLang)) { + return $userLang; + } + } + + try { + // Try to get the language from the Request + $lang = $this->getLanguageFromRequest($app); + if ($userId !== null && $app === null && !$userLang) { + $this->config->setUserValue($userId, 'core', 'lang', $lang); + } + return $lang; + } catch (LanguageNotFoundException $e) { + // Finding language from request failed fall back to default language + $defaultLanguage = $this->config->getSystemValue('default_language', false); + if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) { + return $defaultLanguage; + } + } + + // We could not find any language so fall back to english + return 'en'; + } + + /** + * find the best locale + * + * @param string $lang + * @return null|string + */ + public function findLocale($lang = null) { + $forceLocale = $this->config->getSystemValue('force_locale', false); + if (is_string($forceLocale) && $this->localeExists($forceLocale)) { + return $forceLocale; + } + + if ($this->config->getSystemValue('installed', false)) { + $userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() : null; + $userLocale = null; + if (null !== $userId) { + $userLocale = $this->config->getUserValue($userId, 'core', 'locale', null); + } + } else { + $userId = null; + $userLocale = null; + } + + if ($userLocale && $this->localeExists($userLocale)) { + return $userLocale; + } + + // Default : use system default locale + $defaultLocale = $this->config->getSystemValue('default_locale', false); + if ($defaultLocale !== false && $this->localeExists($defaultLocale)) { + return $defaultLocale; + } + + // If no user locale set, use lang as locale + if (null !== $lang && $this->localeExists($lang)) { + return $lang; + } + + // At last, return USA + return 'en_US'; + } + + /** + * find the matching lang from the locale + * + * @param string $app + * @param string $locale + * @return null|string + */ + public function findLanguageFromLocale(string $app = 'core', string $locale = null) { + if ($this->languageExists($app, $locale)) { + return $locale; + } + + // Try to split e.g: fr_FR => fr + $locale = explode('_', $locale)[0]; + if ($this->languageExists($app, $locale)) { + return $locale; + } + } + + /** + * Find all available languages for an app + * + * @param string|null $app App id or null for core + * @return array an array of available languages + */ + public function findAvailableLanguages($app = null) { + $key = $app; + if ($key === null) { + $key = 'null'; + } + + // also works with null as key + if (!empty($this->availableLanguages[$key])) { + return $this->availableLanguages[$key]; + } + + $available = ['en']; //english is always available + $dir = $this->findL10nDir($app); + if (is_dir($dir)) { + $files = scandir($dir); + if ($files !== false) { + foreach ($files as $file) { + if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') { + $available[] = substr($file, 0, -5); + } + } + } + } + + // merge with translations from theme + $theme = $this->config->getSystemValue('theme'); + if (!empty($theme)) { + $themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot)); + + if (is_dir($themeDir)) { + $files = scandir($themeDir); + if ($files !== false) { + foreach ($files as $file) { + if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') { + $available[] = substr($file, 0, -5); + } + } + } + } + } + + $this->availableLanguages[$key] = $available; + return $available; + } + + /** + * @return array|mixed + */ + public function findAvailableLocales() { + if (!empty($this->availableLocales)) { + return $this->availableLocales; + } + + $localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json'); + $this->availableLocales = \json_decode($localeData, true); + + return $this->availableLocales; + } + + /** + * @param string|null $app App id or null for core + * @param string $lang + * @return bool + */ + public function languageExists($app, $lang) { + if ($lang === 'en') {//english is always available + return true; + } + + $languages = $this->findAvailableLanguages($app); + return array_search($lang, $languages) !== false; + } + + public function getLanguageIterator(IUser $user = null): ILanguageIterator { + $user = $user ?? $this->userSession->getUser(); + if ($user === null) { + throw new \RuntimeException('Failed to get an IUser instance'); + } + return new LanguageIterator($user, $this->config); + } + + /** + * Return the language to use when sending something to a user + * + * @param IUser|null $user + * @return string + * @since 20.0.0 + */ + public function getUserLanguage(IUser $user = null): string { + $language = $this->config->getSystemValue('force_language', false); + if ($language !== false) { + return $language; + } + + if ($user instanceof IUser) { + $language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null); + if ($language !== null) { + return $language; + } + } + + return $this->config->getSystemValue('default_language', 'en'); + } + + /** + * @param string $locale + * @return bool + */ + public function localeExists($locale) { + if ($locale === 'en') { //english is always available + return true; + } + + $locales = $this->findAvailableLocales(); + $userLocale = array_filter($locales, function ($value) use ($locale) { + return $locale === $value['code']; + }); + + return !empty($userLocale); + } + + /** + * @param string|null $app + * @return string + * @throws LanguageNotFoundException + */ + private function getLanguageFromRequest($app) { + $header = $this->request->getHeader('ACCEPT_LANGUAGE'); + if ($header !== '') { + $available = $this->findAvailableLanguages($app); + + // E.g. make sure that 'de' is before 'de_DE'. + sort($available); + + $preferences = preg_split('/,\s*/', strtolower($header)); + foreach ($preferences as $preference) { + list($preferred_language) = explode(';', $preference); + $preferred_language = str_replace('-', '_', $preferred_language); + + foreach ($available as $available_language) { + if ($preferred_language === strtolower($available_language)) { + return $this->respectDefaultLanguage($app, $available_language); + } + } + + // Fallback from de_De to de + foreach ($available as $available_language) { + if (substr($preferred_language, 0, 2) === $available_language) { + return $available_language; + } + } + } + } + + throw new LanguageNotFoundException(); + } + + /** + * if default language is set to de_DE (formal German) this should be + * preferred to 'de' (non-formal German) if possible + * + * @param string|null $app + * @param string $lang + * @return string + */ + protected function respectDefaultLanguage($app, $lang) { + $result = $lang; + $defaultLanguage = $this->config->getSystemValue('default_language', false); + + // use formal version of german ("Sie" instead of "Du") if the default + // language is set to 'de_DE' if possible + if (is_string($defaultLanguage) && + strtolower($lang) === 'de' && + strtolower($defaultLanguage) === 'de_de' && + $this->languageExists($app, 'de_DE') + ) { + $result = 'de_DE'; + } + + return $result; + } + + /** + * Checks if $sub is a subdirectory of $parent + * + * @param string $sub + * @param string $parent + * @return bool + */ + private function isSubDirectory($sub, $parent) { + // Check whether $sub contains no ".." + if (strpos($sub, '..') !== false) { + return false; + } + + // Check whether $sub is a subdirectory of $parent + if (strpos($sub, $parent) === 0) { + return true; + } + + return false; + } + + /** + * Get a list of language files that should be loaded + * + * @param string $app + * @param string $lang + * @return string[] + */ + // FIXME This method is only public, until OC_L10N does not need it anymore, + // FIXME This is also the reason, why it is not in the public interface + public function getL10nFilesForApp($app, $lang) { + $languageFiles = []; + + $i18nDir = $this->findL10nDir($app); + $transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json'; + + if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/') + || $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/') + || $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/') + ) + && file_exists($transFile)) { + // load the translations file + $languageFiles[] = $transFile; + } + + // merge with translations from theme + $theme = $this->config->getSystemValue('theme'); + if (!empty($theme)) { + $transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot)); + if (file_exists($transFile)) { + $languageFiles[] = $transFile; + } + } + + return $languageFiles; + } + + /** + * find the l10n directory + * + * @param string $app App id or empty string for core + * @return string directory + */ + protected function findL10nDir($app = null) { + if (in_array($app, ['core', 'lib'])) { + if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) { + return $this->serverRoot . '/' . $app . '/l10n/'; + } + } elseif ($app && \OC_App::getAppPath($app) !== false) { + // Check if the app is in the app folder + return \OC_App::getAppPath($app) . '/l10n/'; + } + return $this->serverRoot . '/core/l10n/'; + } + + + /** + * Creates a function from the plural string + * + * Parts of the code is copied from Habari: + * https://github.com/habari/system/blob/master/classes/locale.php + * @param string $string + * @return string + */ + public function createPluralFunction($string) { + if (isset($this->pluralFunctions[$string])) { + return $this->pluralFunctions[$string]; + } + + if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) { + // sanitize + $nplurals = preg_replace('/[^0-9]/', '', $matches[1]); + $plural = preg_replace('#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2]); + + $body = str_replace( + [ 'plural', 'n', '$n$plurals', ], + [ '$plural', '$n', '$nplurals', ], + 'nplurals='. $nplurals . '; plural=' . $plural + ); + + // add parents + // important since PHP's ternary evaluates from left to right + $body .= ';'; + $res = ''; + $p = 0; + $length = strlen($body); + for ($i = 0; $i < $length; $i++) { + $ch = $body[$i]; + switch ($ch) { + case '?': + $res .= ' ? ('; + $p++; + break; + case ':': + $res .= ') : ('; + break; + case ';': + $res .= str_repeat(')', $p) . ';'; + $p = 0; + break; + default: + $res .= $ch; + } + } + + $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);'; + $function = create_function('$n', $body); + $this->pluralFunctions[$string] = $function; + return $function; + } else { + // default: one plural form for all cases but n==1 (english) + $function = create_function( + '$n', + '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);' + ); + $this->pluralFunctions[$string] = $function; + return $function; + } + } + + /** + * returns the common language and other languages in an + * associative array + * + * @return array + */ + public function getLanguages() { + $forceLanguage = $this->config->getSystemValue('force_language', false); + if ($forceLanguage !== false) { + $l = $this->get('lib', $forceLanguage); + $potentialName = (string) $l->t('__language_name__'); + + return [ + 'commonlanguages' => [[ + 'code' => $forceLanguage, + 'name' => $potentialName, + ]], + 'languages' => [], + ]; + } + + $languageCodes = $this->findAvailableLanguages(); + + $commonLanguages = []; + $languages = []; + + foreach ($languageCodes as $lang) { + $l = $this->get('lib', $lang); + // TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version + $potentialName = (string) $l->t('__language_name__'); + if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file + $ln = [ + 'code' => $lang, + 'name' => $potentialName + ]; + } elseif ($lang === 'en') { + $ln = [ + 'code' => $lang, + 'name' => 'English (US)' + ]; + } else {//fallback to language code + $ln = [ + 'code' => $lang, + 'name' => $lang + ]; + } + + // put appropriate languages into appropriate arrays, to print them sorted + // common languages -> divider -> other languages + if (in_array($lang, self::COMMON_LANGUAGE_CODES)) { + $commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln; + } else { + $languages[] = $ln; + } + } + + ksort($commonLanguages); + + // sort now by displayed language not the iso-code + usort($languages, function ($a, $b) { + if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) { + // If a doesn't have a name, but b does, list b before a + return 1; + } + if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) { + // If a does have a name, but b doesn't, list a before b + return -1; + } + // Otherwise compare the names + return strcmp($a['name'], $b['name']); + }); + + return [ + // reset indexes + 'commonlanguages' => array_values($commonLanguages), + 'languages' => $languages + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/L10N.php b/docker/overlays/nextcloud/html/lib/private/L10N/L10N.php new file mode 100644 index 0000000..3b32ec2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/L10N.php @@ -0,0 +1,251 @@ + + * @author Georg Ehrke + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Thomas Citharel + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\L10N; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use Punic\Calendar; +use Symfony\Component\Translation\PluralizationRules; + +class L10N implements IL10N { + + /** @var IFactory */ + protected $factory; + + /** @var string App of this object */ + protected $app; + + /** @var string Language of this object */ + protected $lang; + + /** @var string Locale of this object */ + protected $locale; + + /** @var string Plural forms (string) */ + private $pluralFormString = 'nplurals=2; plural=(n != 1);'; + + /** @var string Plural forms (function) */ + private $pluralFormFunction = null; + + /** @var string[] */ + private $translations = []; + + /** + * @param IFactory $factory + * @param string $app + * @param string $lang + * @param string $locale + * @param array $files + */ + public function __construct(IFactory $factory, $app, $lang, $locale, array $files) { + $this->factory = $factory; + $this->app = $app; + $this->lang = $lang; + $this->locale = $locale; + + foreach ($files as $languageFile) { + $this->load($languageFile); + } + } + + /** + * The code (en, de, ...) of the language that is used for this instance + * + * @return string language + */ + public function getLanguageCode(): string { + return $this->lang; + } + + /** + * The code (en_US, fr_CA, ...) of the locale that is used for this instance + * + * @return string locale + */ + public function getLocaleCode(): string { + return $this->locale; + } + + /** + * Translating + * @param string $text The text we need a translation for + * @param array|string $parameters default:array() Parameters for sprintf + * @return string Translation or the same text + * + * Returns the translation. If no translation is found, $text will be + * returned. + */ + public function t(string $text, $parameters = []): string { + if (!\is_array($parameters)) { + $parameters = [$parameters]; + } + + return (string) new L10NString($this, $text, $parameters); + } + + /** + * Translating + * @param string $text_singular the string to translate for exactly one object + * @param string $text_plural the string to translate for n objects + * @param integer $count Number of objects + * @param array $parameters default:array() Parameters for sprintf + * @return string Translation or the same text + * + * Returns the translation. If no translation is found, $text will be + * returned. %n will be replaced with the number of objects. + * + * The correct plural is determined by the plural_forms-function + * provided by the po file. + * + */ + public function n(string $text_singular, string $text_plural, int $count, array $parameters = []): string { + $identifier = "_${text_singular}_::_${text_plural}_"; + if (isset($this->translations[$identifier])) { + return (string) new L10NString($this, $identifier, $parameters, $count); + } + + if ($count === 1) { + return (string) new L10NString($this, $text_singular, $parameters, $count); + } + + return (string) new L10NString($this, $text_plural, $parameters, $count); + } + + /** + * Localization + * @param string $type Type of localization + * @param \DateTime|int|string $data parameters for this localization + * @param array $options + * @return string|int|false + * + * Returns the localized data. + * + * Implemented types: + * - date + * - Creates a date + * - params: timestamp (int/string) + * - datetime + * - Creates date and time + * - params: timestamp (int/string) + * - time + * - Creates a time + * - params: timestamp (int/string) + * - firstday: Returns the first day of the week (0 sunday - 6 saturday) + * - jsdate: Returns the short JS date format + */ + public function l(string $type, $data = null, array $options = []) { + if (null === $this->locale) { + // Use the language of the instance + $this->locale = $this->getLanguageCode(); + } + if ($this->locale === 'sr@latin') { + $this->locale = 'sr_latn'; + } + + if ($type === 'firstday') { + return (int) Calendar::getFirstWeekday($this->locale); + } + if ($type === 'jsdate') { + return (string) Calendar::getDateFormat('short', $this->locale); + } + + $value = new \DateTime(); + if ($data instanceof \DateTime) { + $value = $data; + } elseif (\is_string($data) && !is_numeric($data)) { + $data = strtotime($data); + $value->setTimestamp($data); + } elseif ($data !== null) { + $data = (int)$data; + $value->setTimestamp($data); + } + + $options = array_merge(['width' => 'long'], $options); + $width = $options['width']; + switch ($type) { + case 'date': + return (string) Calendar::formatDate($value, $width, $this->locale); + case 'datetime': + return (string) Calendar::formatDatetime($value, $width, $this->locale); + case 'time': + return (string) Calendar::formatTime($value, $width, $this->locale); + case 'weekdayName': + return (string) Calendar::getWeekdayName($value, $width, $this->locale); + default: + return false; + } + } + + /** + * Returns an associative array with all translations + * + * Called by \OC_L10N_String + * @return array + */ + public function getTranslations(): array { + return $this->translations; + } + + /** + * Returnsed function accepts the argument $n + * + * Called by \OC_L10N_String + * @return \Closure the plural form function + */ + public function getPluralFormFunction(): \Closure { + if (\is_null($this->pluralFormFunction)) { + $lang = $this->getLanguageCode(); + $this->pluralFormFunction = function ($n) use ($lang) { + return PluralizationRules::get($n, $lang); + }; + } + + return $this->pluralFormFunction; + } + + /** + * @param string $translationFile + * @return bool + */ + protected function load(string $translationFile): bool { + $json = json_decode(file_get_contents($translationFile), true); + if (!\is_array($json)) { + $jsonError = json_last_error(); + \OC::$server->getLogger()->warning("Failed to load $translationFile - json error code: $jsonError", ['app' => 'l10n']); + return false; + } + + if (!empty($json['pluralForm'])) { + $this->pluralFormString = $json['pluralForm']; + } + $this->translations = array_merge($this->translations, $json['translations']); + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/L10NString.php b/docker/overlays/nextcloud/html/lib/private/L10N/L10NString.php new file mode 100644 index 0000000..a822281 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/L10NString.php @@ -0,0 +1,89 @@ + + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\L10N; + +class L10NString implements \JsonSerializable { + /** @var \OC\L10N\L10N */ + protected $l10n; + + /** @var string */ + protected $text; + + /** @var array */ + protected $parameters; + + /** @var integer */ + protected $count; + + /** + * @param \OC\L10N\L10N $l10n + * @param string|string[] $text + * @param array $parameters + * @param int $count + */ + public function __construct(\OC\L10N\L10N $l10n, $text, $parameters, $count = 1) { + $this->l10n = $l10n; + $this->text = $text; + $this->parameters = $parameters; + $this->count = $count; + } + + /** + * @return string + */ + public function __toString() { + $translations = $this->l10n->getTranslations(); + + $text = $this->text; + if (array_key_exists($this->text, $translations)) { + if (is_array($translations[$this->text])) { + $fn = $this->l10n->getPluralFormFunction(); + $id = $fn($this->count); + $text = $translations[$this->text][$id]; + } else { + $text = $translations[$this->text]; + } + } + + // Replace %n first (won't interfere with vsprintf) + $text = str_replace('%n', (string)$this->count, $text); + return vsprintf($text, $this->parameters); + } + + + /** + * @return string + */ + public function jsonSerialize() { + return $this->__toString(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/LanguageIterator.php b/docker/overlays/nextcloud/html/lib/private/L10N/LanguageIterator.php new file mode 100644 index 0000000..cbbafde --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/LanguageIterator.php @@ -0,0 +1,145 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\L10N; + +use OCP\IConfig; +use OCP\IUser; +use OCP\L10N\ILanguageIterator; + +class LanguageIterator implements ILanguageIterator { + private $i = 0; + /** @var IConfig */ + private $config; + /** @var IUser */ + private $user; + + public function __construct(IUser $user, IConfig $config) { + $this->config = $config; + $this->user = $user; + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() { + $this->i = 0; + } + + /** + * Return the current element + * + * @since 14.0.0 + */ + public function current(): string { + switch ($this->i) { + /** @noinspection PhpMissingBreakStatementInspection */ + case 0: + $forcedLang = $this->config->getSystemValue('force_language', false); + if (is_string($forcedLang)) { + return $forcedLang; + } + $this->next(); + /** @noinspection PhpMissingBreakStatementInspection */ + // no break + case 1: + $forcedLang = $this->config->getSystemValue('force_language', false); + if (is_string($forcedLang) + && ($truncated = $this->getTruncatedLanguage($forcedLang)) !== $forcedLang + ) { + return $truncated; + } + $this->next(); + /** @noinspection PhpMissingBreakStatementInspection */ + // no break + case 2: + $userLang = $this->config->getUserValue($this->user->getUID(), 'core', 'lang', null); + if (is_string($userLang)) { + return $userLang; + } + $this->next(); + /** @noinspection PhpMissingBreakStatementInspection */ + // no break + case 3: + $userLang = $this->config->getUserValue($this->user->getUID(), 'core', 'lang', null); + if (is_string($userLang) + && ($truncated = $this->getTruncatedLanguage($userLang)) !== $userLang + ) { + return $truncated; + } + $this->next(); + // no break + case 4: + return $this->config->getSystemValue('default_language', 'en'); + /** @noinspection PhpMissingBreakStatementInspection */ + case 5: + $defaultLang = $this->config->getSystemValue('default_language', 'en'); + if (($truncated = $this->getTruncatedLanguage($defaultLang)) !== $defaultLang) { + return $truncated; + } + $this->next(); + // no break + default: + return 'en'; + } + } + + /** + * Move forward to next element + * + * @since 14.0.0 + */ + public function next() { + ++$this->i; + } + + /** + * Return the key of the current element + * + * @since 14.0.0 + */ + public function key(): int { + return $this->i; + } + + /** + * Checks if current position is valid + * + * @since 14.0.0 + */ + public function valid(): bool { + return $this->i <= 6; + } + + protected function getTruncatedLanguage(string $lang):string { + $pos = strpos($lang, '_'); + if ($pos !== false) { + $lang = substr($lang, 0, $pos); + } + return $lang; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/LanguageNotFoundException.php b/docker/overlays/nextcloud/html/lib/private/L10N/LanguageNotFoundException.php new file mode 100644 index 0000000..20b22a8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/LanguageNotFoundException.php @@ -0,0 +1,27 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\L10N; + +class LanguageNotFoundException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/L10N/LazyL10N.php b/docker/overlays/nextcloud/html/lib/private/L10N/LazyL10N.php new file mode 100644 index 0000000..9ffcb4d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/L10N/LazyL10N.php @@ -0,0 +1,71 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\L10N; + +use OCP\IL10N; + +class LazyL10N implements IL10N { + + /** @var IL10N */ + private $l; + + /** @var \Closure */ + private $factory; + + + public function __construct(\Closure $factory) { + $this->factory = $factory; + } + + private function getL(): IL10N { + if ($this->l === null) { + $this->l = ($this->factory)(); + } + + return $this->l; + } + + public function t(string $text, $parameters = []): string { + return $this->getL()->t($text, $parameters); + } + + public function n(string $text_singular, string $text_plural, int $count, array $parameters = []): string { + return $this->getL()->n($text_singular, $text_plural, $count, $parameters); + } + + public function l(string $type, $data, array $options = []) { + return $this->getL()->l($type, $data, $options); + } + + public function getLanguageCode(): string { + return $this->getL()->getLanguageCode(); + } + + public function getLocaleCode(): string { + return $this->getL()->getLocaleCode(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/LargeFileHelper.php b/docker/overlays/nextcloud/html/lib/private/LargeFileHelper.php new file mode 100644 index 0000000..6d03e78 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/LargeFileHelper.php @@ -0,0 +1,213 @@ + + * + * @author Andreas Fischer + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author marco44 + * @author Michael Roitzsch + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use bantu\IniGetWrapper\IniGetWrapper; + +/** + * Helper class for large files on 32-bit platforms. + */ +class LargeFileHelper { + /** + * pow(2, 53) as a base-10 string. + * @var string + */ + public const POW_2_53 = '9007199254740992'; + + /** + * pow(2, 53) - 1 as a base-10 string. + * @var string + */ + public const POW_2_53_MINUS_1 = '9007199254740991'; + + /** + * @brief Checks whether our assumptions hold on the PHP platform we are on. + * + * @throws \RunTimeException if our assumptions do not hold on the current + * PHP platform. + */ + public function __construct() { + $pow_2_53 = (float)self::POW_2_53_MINUS_1 + 1.0; + if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) { + throw new \RuntimeException( + 'This class assumes floats to be double precision or "better".' + ); + } + } + + /** + * @brief Formats a signed integer or float as an unsigned integer base-10 + * string. Passed strings will be checked for being base-10. + * + * @param int|float|string $number Number containing unsigned integer data + * + * @throws \UnexpectedValueException if $number is not a float, not an int + * and not a base-10 string. + * + * @return string Unsigned integer base-10 string + */ + public function formatUnsignedInteger($number) { + if (is_float($number)) { + // Undo the effect of the php.ini setting 'precision'. + return number_format($number, 0, '', ''); + } elseif (is_string($number) && ctype_digit($number)) { + return $number; + } elseif (is_int($number)) { + // Interpret signed integer as unsigned integer. + return sprintf('%u', $number); + } else { + throw new \UnexpectedValueException( + 'Expected int, float or base-10 string' + ); + } + } + + /** + * @brief Tries to get the size of a file via various workarounds that + * even work for large files on 32-bit platforms. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSize($filename) { + $fileSize = $this->getFileSizeViaCurl($filename); + if (!is_null($fileSize)) { + return $fileSize; + } + $fileSize = $this->getFileSizeViaExec($filename); + if (!is_null($fileSize)) { + return $fileSize; + } + return $this->getFileSizeNative($filename); + } + + /** + * @brief Tries to get the size of a file via a CURL HEAD request. + * + * @param string $fileName Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSizeViaCurl($fileName) { + if (\OC::$server->get(IniGetWrapper::class)->getString('open_basedir') === '') { + $encodedFileName = rawurlencode($fileName); + $ch = curl_init("file:///$encodedFileName"); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + $data = curl_exec($ch); + curl_close($ch); + if ($data !== false) { + $matches = []; + preg_match('/Content-Length: (\d+)/', $data, $matches); + if (isset($matches[1])) { + return 0 + $matches[1]; + } + } + } + return null; + } + + /** + * @brief Tries to get the size of a file via an exec() call. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSizeViaExec($filename) { + if (\OC_Helper::is_function_enabled('exec')) { + $os = strtolower(php_uname('s')); + $arg = escapeshellarg($filename); + $result = null; + if (strpos($os, 'linux') !== false) { + $result = $this->exec("stat -c %s $arg"); + } elseif (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) { + $result = $this->exec("stat -f %z $arg"); + } + return $result; + } + return null; + } + + /** + * @brief Gets the size of a file via a filesize() call and converts + * negative signed int to positive float. As the result of filesize() + * will wrap around after a file size of 2^32 bytes = 4 GiB, this + * should only be used as a last resort. + * + * @param string $filename Path to the file. + * + * @return int|float Number of bytes as number (float or int). + */ + public function getFileSizeNative($filename) { + $result = filesize($filename); + if ($result < 0) { + // For file sizes between 2 GiB and 4 GiB, filesize() will return a + // negative int, as the PHP data type int is signed. Interpret the + // returned int as an unsigned integer and put it into a float. + return (float) sprintf('%u', $result); + } + return $result; + } + + /** + * Returns the current mtime for $fullPath + * + * @param string $fullPath + * @return int + */ + public function getFileMtime($fullPath) { + try { + $result = filemtime($fullPath); + } catch (\Exception $e) { + $result =- 1; + } + if ($result < 0) { + if (\OC_Helper::is_function_enabled('exec')) { + $os = strtolower(php_uname('s')); + if (strpos($os, 'linux') !== false) { + return $this->exec('stat -c %Y ' . escapeshellarg($fullPath)); + } + } + } + return $result; + } + + protected function exec($cmd) { + $result = trim(exec($cmd)); + return ctype_digit($result) ? 0 + $result : null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lock/AbstractLockingProvider.php b/docker/overlays/nextcloud/html/lib/private/Lock/AbstractLockingProvider.php new file mode 100644 index 0000000..e23e856 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lock/AbstractLockingProvider.php @@ -0,0 +1,133 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Lock; + +use OCP\Lock\ILockingProvider; + +/** + * Base locking provider that keeps track of locks acquired during the current request + * to release any left over locks at the end of the request + */ +abstract class AbstractLockingProvider implements ILockingProvider { + /** @var int $ttl */ + protected $ttl; // how long until we clear stray locks in seconds + + protected $acquiredLocks = [ + 'shared' => [], + 'exclusive' => [] + ]; + + /** + * Check if we've locally acquired a lock + * + * @param string $path + * @param int $type + * @return bool + */ + protected function hasAcquiredLock(string $path, int $type): bool { + if ($type === self::LOCK_SHARED) { + return isset($this->acquiredLocks['shared'][$path]) && $this->acquiredLocks['shared'][$path] > 0; + } else { + return isset($this->acquiredLocks['exclusive'][$path]) && $this->acquiredLocks['exclusive'][$path] === true; + } + } + + /** + * Mark a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markAcquire(string $path, int $type) { + if ($type === self::LOCK_SHARED) { + if (!isset($this->acquiredLocks['shared'][$path])) { + $this->acquiredLocks['shared'][$path] = 0; + } + $this->acquiredLocks['shared'][$path]++; + } else { + $this->acquiredLocks['exclusive'][$path] = true; + } + } + + /** + * Mark a release of a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markRelease(string $path, int $type) { + if ($type === self::LOCK_SHARED) { + if (isset($this->acquiredLocks['shared'][$path]) and $this->acquiredLocks['shared'][$path] > 0) { + $this->acquiredLocks['shared'][$path]--; + if ($this->acquiredLocks['shared'][$path] === 0) { + unset($this->acquiredLocks['shared'][$path]); + } + } + } elseif ($type === self::LOCK_EXCLUSIVE) { + unset($this->acquiredLocks['exclusive'][$path]); + } + } + + /** + * Change the type of an existing tracked lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markChange(string $path, int $targetType) { + if ($targetType === self::LOCK_SHARED) { + unset($this->acquiredLocks['exclusive'][$path]); + if (!isset($this->acquiredLocks['shared'][$path])) { + $this->acquiredLocks['shared'][$path] = 0; + } + $this->acquiredLocks['shared'][$path]++; + } elseif ($targetType === self::LOCK_EXCLUSIVE) { + $this->acquiredLocks['exclusive'][$path] = true; + $this->acquiredLocks['shared'][$path]--; + } + } + + /** + * release all lock acquired by this instance which were marked using the mark* methods + */ + public function releaseAll() { + foreach ($this->acquiredLocks['shared'] as $path => $count) { + for ($i = 0; $i < $count; $i++) { + $this->releaseLock($path, self::LOCK_SHARED); + } + } + + foreach ($this->acquiredLocks['exclusive'] as $path => $hasLock) { + $this->releaseLock($path, self::LOCK_EXCLUSIVE); + } + } + + protected function getOwnSharedLockCount(string $path) { + return isset($this->acquiredLocks['shared'][$path]) ? $this->acquiredLocks['shared'][$path] : 0; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lock/DBLockingProvider.php b/docker/overlays/nextcloud/html/lib/private/Lock/DBLockingProvider.php new file mode 100644 index 0000000..2ad7c16 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lock/DBLockingProvider.php @@ -0,0 +1,315 @@ + + * @author Individual IT Services + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Ole Ostergaard + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Lock; + +use OC\DB\QueryBuilder\Literal; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +/** + * Locking provider that stores the locks in the database + */ +class DBLockingProvider extends AbstractLockingProvider { + /** + * @var \OCP\IDBConnection + */ + private $connection; + + /** + * @var \OCP\ILogger + */ + private $logger; + + /** + * @var \OCP\AppFramework\Utility\ITimeFactory + */ + private $timeFactory; + + private $sharedLocks = []; + + /** + * @var bool + */ + private $cacheSharedLocks; + + /** + * Check if we have an open shared lock for a path + * + * @param string $path + * @return bool + */ + protected function isLocallyLocked(string $path): bool { + return isset($this->sharedLocks[$path]) && $this->sharedLocks[$path]; + } + + /** + * Mark a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markAcquire(string $path, int $type) { + parent::markAcquire($path, $type); + if ($this->cacheSharedLocks) { + if ($type === self::LOCK_SHARED) { + $this->sharedLocks[$path] = true; + } + } + } + + /** + * Change the type of an existing tracked lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markChange(string $path, int $targetType) { + parent::markChange($path, $targetType); + if ($this->cacheSharedLocks) { + if ($targetType === self::LOCK_SHARED) { + $this->sharedLocks[$path] = true; + } elseif ($targetType === self::LOCK_EXCLUSIVE) { + $this->sharedLocks[$path] = false; + } + } + } + + /** + * @param \OCP\IDBConnection $connection + * @param \OCP\ILogger $logger + * @param \OCP\AppFramework\Utility\ITimeFactory $timeFactory + * @param int $ttl + * @param bool $cacheSharedLocks + */ + public function __construct( + IDBConnection $connection, + ILogger $logger, + ITimeFactory $timeFactory, + int $ttl = 3600, + $cacheSharedLocks = true + ) { + $this->connection = $connection; + $this->logger = $logger; + $this->timeFactory = $timeFactory; + $this->ttl = $ttl; + $this->cacheSharedLocks = $cacheSharedLocks; + } + + /** + * Insert a file locking row if it does not exists. + * + * @param string $path + * @param int $lock + * @return int number of inserted rows + */ + protected function initLockField(string $path, int $lock = 0): int { + $expire = $this->getExpireTime(); + return $this->connection->insertIgnoreConflict('file_locks', [ + 'key' => $path, + 'lock' => $lock, + 'ttl' => $expire + ]); + } + + /** + * @return int + */ + protected function getExpireTime(): int { + return $this->timeFactory->getTime() + $this->ttl; + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @return bool + */ + public function isLocked(string $path, int $type): bool { + if ($this->hasAcquiredLock($path, $type)) { + return true; + } + $query = $this->connection->prepare('SELECT `lock` from `*PREFIX*file_locks` WHERE `key` = ?'); + $query->execute([$path]); + $lockValue = (int)$query->fetchColumn(); + if ($type === self::LOCK_SHARED) { + if ($this->isLocallyLocked($path)) { + // if we have a shared lock we kept open locally but it's released we always have at least 1 shared lock in the db + return $lockValue > 1; + } else { + return $lockValue > 0; + } + } elseif ($type === self::LOCK_EXCLUSIVE) { + return $lockValue === -1; + } else { + return false; + } + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function acquireLock(string $path, int $type, string $readablePath = null) { + $expire = $this->getExpireTime(); + if ($type === self::LOCK_SHARED) { + if (!$this->isLocallyLocked($path)) { + $result = $this->initLockField($path, 1); + if ($result <= 0) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` + 1, `ttl` = ? WHERE `key` = ? AND `lock` >= 0', + [$expire, $path] + ); + } + } else { + $result = 1; + } + } else { + $existing = 0; + if ($this->hasAcquiredLock($path, ILockingProvider::LOCK_SHARED) === false && $this->isLocallyLocked($path)) { + $existing = 1; + } + $result = $this->initLockField($path, -1); + if ($result <= 0) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = ?', + [$expire, $path, $existing] + ); + } + } + if ($result !== 1) { + throw new LockedException($path, null, null, $readablePath); + } + $this->markAcquire($path, $type); + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + public function releaseLock(string $path, int $type) { + $this->markRelease($path, $type); + + // we keep shared locks till the end of the request so we can re-use them + if ($type === self::LOCK_EXCLUSIVE) { + $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = 0 WHERE `key` = ? AND `lock` = -1', + [$path] + ); + } elseif (!$this->cacheSharedLocks) { + $query = $this->connection->getQueryBuilder(); + $query->update('file_locks') + ->set('lock', $query->func()->subtract('lock', $query->createNamedParameter(1))) + ->where($query->expr()->eq('key', $query->createNamedParameter($path))) + ->andWhere($query->expr()->gt('lock', $query->createNamedParameter(0))); + $query->execute(); + } + } + + /** + * Change the type of an existing lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function changeLock(string $path, int $targetType) { + $expire = $this->getExpireTime(); + if ($targetType === self::LOCK_SHARED) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = 1, `ttl` = ? WHERE `key` = ? AND `lock` = -1', + [$expire, $path] + ); + } else { + // since we only keep one shared lock in the db we need to check if we have more then one shared lock locally manually + if (isset($this->acquiredLocks['shared'][$path]) && $this->acquiredLocks['shared'][$path] > 1) { + throw new LockedException($path); + } + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = 1', + [$expire, $path] + ); + } + if ($result !== 1) { + throw new LockedException($path); + } + $this->markChange($path, $targetType); + } + + /** + * cleanup empty locks + */ + public function cleanExpiredLocks() { + $expire = $this->timeFactory->getTime(); + try { + $this->connection->executeUpdate( + 'DELETE FROM `*PREFIX*file_locks` WHERE `ttl` < ?', + [$expire] + ); + } catch (\Exception $e) { + // If the table is missing, the clean up was successful + if ($this->connection->tableExists('file_locks')) { + throw $e; + } + } + } + + /** + * release all lock acquired by this instance which were marked using the mark* methods + */ + public function releaseAll() { + parent::releaseAll(); + + if (!$this->cacheSharedLocks) { + return; + } + // since we keep shared locks we need to manually clean those + $lockedPaths = array_keys($this->sharedLocks); + $lockedPaths = array_filter($lockedPaths, function ($path) { + return $this->sharedLocks[$path]; + }); + + $chunkedPaths = array_chunk($lockedPaths, 100); + + foreach ($chunkedPaths as $chunk) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('file_locks') + ->set('lock', $builder->func()->subtract('lock', $builder->expr()->literal(1))) + ->where($builder->expr()->in('key', $builder->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))) + ->andWhere($builder->expr()->gt('lock', new Literal(0))); + + $query->execute(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lock/MemcacheLockingProvider.php b/docker/overlays/nextcloud/html/lib/private/Lock/MemcacheLockingProvider.php new file mode 100644 index 0000000..6b01f0a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lock/MemcacheLockingProvider.php @@ -0,0 +1,156 @@ + + * @author Jaakko Salo + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Lock; + +use OCP\IMemcache; +use OCP\IMemcacheTTL; +use OCP\Lock\LockedException; + +class MemcacheLockingProvider extends AbstractLockingProvider { + /** + * @var \OCP\IMemcache + */ + private $memcache; + + /** + * @param \OCP\IMemcache $memcache + * @param int $ttl + */ + public function __construct(IMemcache $memcache, int $ttl = 3600) { + $this->memcache = $memcache; + $this->ttl = $ttl; + } + + private function setTTL(string $path) { + if ($this->memcache instanceof IMemcacheTTL) { + $this->memcache->setTTL($path, $this->ttl); + } + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @return bool + */ + public function isLocked(string $path, int $type): bool { + $lockValue = $this->memcache->get($path); + if ($type === self::LOCK_SHARED) { + return $lockValue > 0; + } elseif ($type === self::LOCK_EXCLUSIVE) { + return $lockValue === 'exclusive'; + } else { + return false; + } + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @param string $readablePath human readable path to use in error messages + * @throws \OCP\Lock\LockedException + */ + public function acquireLock(string $path, int $type, string $readablePath = null) { + if ($type === self::LOCK_SHARED) { + if (!$this->memcache->inc($path)) { + throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath); + } + } else { + $this->memcache->add($path, 0); + if (!$this->memcache->cas($path, 0, 'exclusive')) { + throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath); + } + } + $this->setTTL($path); + $this->markAcquire($path, $type); + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + public function releaseLock(string $path, int $type) { + if ($type === self::LOCK_SHARED) { + $ownSharedLockCount = $this->getOwnSharedLockCount($path); + $newValue = 0; + if ($ownSharedLockCount === 0) { // if we are not holding the lock, don't try to release it + return; + } + if ($ownSharedLockCount === 1) { + $removed = $this->memcache->cad($path, 1); // if we're the only one having a shared lock we can remove it in one go + if (!$removed) { //someone else also has a shared lock, decrease only + $newValue = $this->memcache->dec($path); + } + } else { + // if we own more than one lock ourselves just decrease + $newValue = $this->memcache->dec($path); + } + + // if we somehow release more locks then exists, reset the lock + if ($newValue < 0) { + $this->memcache->cad($path, $newValue); + } + } elseif ($type === self::LOCK_EXCLUSIVE) { + $this->memcache->cad($path, 'exclusive'); + } + $this->markRelease($path, $type); + } + + /** + * Change the type of an existing lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function changeLock(string $path, int $targetType) { + if ($targetType === self::LOCK_SHARED) { + if (!$this->memcache->cas($path, 'exclusive', 1)) { + throw new LockedException($path, null, $this->getExistingLockForException($path)); + } + } elseif ($targetType === self::LOCK_EXCLUSIVE) { + // we can only change a shared lock to an exclusive if there's only a single owner of the shared lock + if (!$this->memcache->cas($path, 1, 'exclusive')) { + throw new LockedException($path, null, $this->getExistingLockForException($path)); + } + } + $this->setTTL($path); + $this->markChange($path, $targetType); + } + + private function getExistingLockForException($path) { + $existing = $this->memcache->get($path); + if (!$existing) { + return 'none'; + } elseif ($existing === 'exclusive') { + return $existing; + } else { + return $existing . ' shared locks'; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lock/NoopLockingProvider.php b/docker/overlays/nextcloud/html/lib/private/Lock/NoopLockingProvider.php new file mode 100644 index 0000000..4f38d51 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lock/NoopLockingProvider.php @@ -0,0 +1,73 @@ + + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Lock; + +use OCP\Lock\ILockingProvider; + +/** + * Locking provider that does nothing. + * + * To be used when locking is disabled. + */ +class NoopLockingProvider implements ILockingProvider { + + /** + * {@inheritdoc} + */ + public function isLocked(string $path, int $type): bool { + return false; + } + + /** + * {@inheritdoc} + */ + public function acquireLock(string $path, int $type, string $readablePath = null) { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function releaseLock(string $path, int $type) { + // do nothing + } + + /**1 + * {@inheritdoc} + */ + public function releaseAll() { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function changeLock(string $path, int $targetType) { + // do nothing + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullCache.php b/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullCache.php new file mode 100644 index 0000000..396bf68 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullCache.php @@ -0,0 +1,125 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Lockdown\Filesystem; + +use OC\Files\Cache\CacheEntry; +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\FileInfo; +use OCP\Files\Search\ISearchQuery; + +class NullCache implements ICache { + public function getNumericStorageId() { + return -1; + } + + public function get($file) { + return $file !== '' ? null : + new CacheEntry([ + 'fileid' => -1, + 'parent' => -1, + 'name' => '', + 'path' => '', + 'size' => '0', + 'mtime' => time(), + 'storage_mtime' => time(), + 'etag' => '', + 'mimetype' => FileInfo::MIMETYPE_FOLDER, + 'mimepart' => 'httpd', + 'permissions' => Constants::PERMISSION_READ + ]); + } + + public function getFolderContents($folder) { + return []; + } + + public function getFolderContentsById($fileId) { + return []; + } + + public function put($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function insert($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function update($id, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getId($file) { + return -1; + } + + public function getParentId($file) { + return -1; + } + + public function inCache($file) { + return $file === ''; + } + + public function remove($file) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function move($source, $target) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getStatus($file) { + return ICache::COMPLETE; + } + + public function search($pattern) { + return []; + } + + public function searchByMime($mimetype) { + return []; + } + + public function searchQuery(ISearchQuery $query) { + return []; + } + + public function getIncomplete() { + return []; + } + + public function getPathById($id) { + return ''; + } + + public function normalize($path) { + return $path; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullStorage.php b/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullStorage.php new file mode 100644 index 0000000..72bdeeb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lockdown/Filesystem/NullStorage.php @@ -0,0 +1,184 @@ + + * + * @author Lukas Reschke + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Lockdown\Filesystem; + +use Icewind\Streams\IteratorDirectory; +use OC\Files\FileInfo; +use OC\Files\Storage\Common; +use OCP\Files\Storage\IStorage; + +class NullStorage extends Common { + public function __construct($parameters) { + parent::__construct($parameters); + } + + public function getId() { + return 'null'; + } + + public function mkdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rmdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function opendir($path) { + return new IteratorDirectory([]); + } + + public function is_dir($path) { + return $path === ''; + } + + public function is_file($path) { + return false; + } + + public function stat($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function filetype($path) { + return ($path === '') ? 'dir' : false; + } + + public function filesize($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function isCreatable($path) { + return false; + } + + public function isReadable($path) { + return $path === ''; + } + + public function isUpdatable($path) { + return false; + } + + public function isDeletable($path) { + return false; + } + + public function isSharable($path) { + return false; + } + + public function getPermissions($path) { + return null; + } + + public function file_exists($path) { + return $path === ''; + } + + public function filemtime($path) { + return ($path === '') ? time() : false; + } + + public function file_get_contents($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function file_put_contents($path, $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function unlink($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rename($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function copy($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function fopen($path, $mode) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getMimeType($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function hash($type, $path, $raw = false) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function free_space($path) { + return FileInfo::SPACE_UNKNOWN; + } + + public function touch($path, $mtime = null) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getLocalFile($path) { + return false; + } + + public function hasUpdated($path, $time) { + return false; + } + + public function getETag($path) { + return ''; + } + + public function isLocal() { + return false; + } + + public function getDirectDownload($path) { + return false; + } + + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function test() { + return true; + } + + public function getOwner($path) { + return null; + } + + public function getCache($path = '', $storage = null) { + return new NullCache(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Lockdown/LockdownManager.php b/docker/overlays/nextcloud/html/lib/private/Lockdown/LockdownManager.php new file mode 100644 index 0000000..2883ee0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Lockdown/LockdownManager.php @@ -0,0 +1,83 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Lockdown; + +use OC\Authentication\Token\IToken; +use OCP\ISession; +use OCP\Lockdown\ILockdownManager; + +class LockdownManager implements ILockdownManager { + /** @var ISession */ + private $sessionCallback; + + private $enabled = false; + + /** @var array|null */ + private $scope; + + /** + * LockdownManager constructor. + * + * @param callable $sessionCallback we need to inject the session lazily to avoid dependency loops + */ + public function __construct(callable $sessionCallback) { + $this->sessionCallback = $sessionCallback; + } + + + public function enable() { + $this->enabled = true; + } + + /** + * @return ISession + */ + private function getSession() { + $callback = $this->sessionCallback; + return $callback(); + } + + private function getScopeAsArray() { + if (!$this->scope) { + $session = $this->getSession(); + $sessionScope = $session->get('token_scope'); + if ($sessionScope) { + $this->scope = $sessionScope; + } + } + return $this->scope; + } + + public function setToken(IToken $token) { + $this->scope = $token->getScopeAsArray(); + $session = $this->getSession(); + $session->set('token_scope', $this->scope); + $this->enable(); + } + + public function canAccessFilesystem() { + $scope = $this->getScopeAsArray(); + return !$scope || $scope['filesystem']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log.php b/docker/overlays/nextcloud/html/lib/private/Log.php new file mode 100644 index 0000000..2048d60 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log.php @@ -0,0 +1,381 @@ + + * @author Bart Visscher + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\Log\IDataLogger; +use function array_merge; +use InterfaSys\LogNormalizer\Normalizer; + +use OC\Log\ExceptionSerializer; +use OCP\ILogger; +use OCP\Log\IFileBased; +use OCP\Log\IWriter; +use OCP\Support\CrashReport\IRegistry; + +/** + * logging utilities + * + * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface + * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md + * for the full interface specification. + * + * MonoLog is an example implementing this interface. + */ +class Log implements ILogger, IDataLogger { + + /** @var IWriter */ + private $logger; + + /** @var SystemConfig */ + private $config; + + /** @var boolean|null cache the result of the log condition check for the request */ + private $logConditionSatisfied = null; + + /** @var Normalizer */ + private $normalizer; + + /** @var IRegistry */ + private $crashReporters; + + /** + * @param IWriter $logger The logger that should be used + * @param SystemConfig $config the system config object + * @param Normalizer|null $normalizer + * @param IRegistry|null $registry + */ + public function __construct(IWriter $logger, SystemConfig $config = null, $normalizer = null, IRegistry $registry = null) { + // FIXME: Add this for backwards compatibility, should be fixed at some point probably + if ($config === null) { + $config = \OC::$server->getSystemConfig(); + } + + $this->config = $config; + $this->logger = $logger; + if ($normalizer === null) { + $this->normalizer = new Normalizer(); + } else { + $this->normalizer = $normalizer; + } + $this->crashReporters = $registry; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * @return void + */ + public function emergency(string $message, array $context = []) { + $this->log(ILogger::FATAL, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return void + */ + public function alert(string $message, array $context = []) { + $this->log(ILogger::ERROR, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return void + */ + public function critical(string $message, array $context = []) { + $this->log(ILogger::ERROR, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return void + */ + public function error(string $message, array $context = []) { + $this->log(ILogger::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return void + */ + public function warning(string $message, array $context = []) { + $this->log(ILogger::WARN, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return void + */ + public function notice(string $message, array $context = []) { + $this->log(ILogger::INFO, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return void + */ + public function info(string $message, array $context = []) { + $this->log(ILogger::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return void + */ + public function debug(string $message, array $context = []) { + $this->log(ILogger::DEBUG, $message, $context); + } + + + /** + * Logs with an arbitrary level. + * + * @param int $level + * @param string $message + * @param array $context + * @return void + */ + public function log(int $level, string $message, array $context = []) { + $minLevel = $this->getLogLevel($context); + + array_walk($context, [$this->normalizer, 'format']); + + $app = $context['app'] ?? 'no app in context'; + + // interpolate $message as defined in PSR-3 + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + $message = strtr($message, $replace); + + try { + if ($level >= $minLevel) { + $this->writeLog($app, $message, $level); + + if ($this->crashReporters !== null) { + $messageContext = array_merge( + $context, + [ + 'level' => $level + ] + ); + $this->crashReporters->delegateMessage($message, $messageContext); + } + } else { + if ($this->crashReporters !== null) { + $this->crashReporters->delegateBreadcrumb($message, 'log', $context); + } + } + } catch (\Throwable $e) { + // make sure we dont hard crash if logging fails + } + } + + private function getLogLevel($context) { + $logCondition = $this->config->getValue('log.condition', []); + + /** + * check for a special log condition - this enables an increased log on + * a per request/user base + */ + if ($this->logConditionSatisfied === null) { + // default to false to just process this once per request + $this->logConditionSatisfied = false; + if (!empty($logCondition)) { + + // check for secret token in the request + if (isset($logCondition['shared_secret'])) { + $request = \OC::$server->getRequest(); + + if ($request->getMethod() === 'PUT' && + strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false && + strpos($request->getHeader('Content-Type'), 'application/json') === false) { + $logSecretRequest = ''; + } else { + $logSecretRequest = $request->getParam('log_secret', ''); + } + + // if token is found in the request change set the log condition to satisfied + if ($request && hash_equals($logCondition['shared_secret'], $logSecretRequest)) { + $this->logConditionSatisfied = true; + } + } + + // check for user + if (isset($logCondition['users'])) { + $user = \OC::$server->getUserSession()->getUser(); + + // if the user matches set the log condition to satisfied + if ($user !== null && in_array($user->getUID(), $logCondition['users'], true)) { + $this->logConditionSatisfied = true; + } + } + } + } + + // if log condition is satisfied change the required log level to DEBUG + if ($this->logConditionSatisfied) { + return ILogger::DEBUG; + } + + if (isset($context['app'])) { + $app = $context['app']; + + /** + * check log condition based on the context of each log message + * once this is met -> change the required log level to debug + */ + if (!empty($logCondition) + && isset($logCondition['apps']) + && in_array($app, $logCondition['apps'], true)) { + return ILogger::DEBUG; + } + } + + return min($this->config->getValue('loglevel', ILogger::WARN), ILogger::FATAL); + } + + /** + * Logs an exception very detailed + * + * @param \Exception|\Throwable $exception + * @param array $context + * @return void + * @since 8.2.0 + */ + public function logException(\Throwable $exception, array $context = []) { + $app = $context['app'] ?? 'no app in context'; + $level = $context['level'] ?? ILogger::ERROR; + + $serializer = new ExceptionSerializer(); + $data = $serializer->serializeException($exception); + $data['CustomMessage'] = $context['message'] ?? '--'; + + $minLevel = $this->getLogLevel($context); + + array_walk($context, [$this->normalizer, 'format']); + + try { + if ($level >= $minLevel) { + if (!$this->logger instanceof IFileBased) { + $data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES); + } + $this->writeLog($app, $data, $level); + } + + $context['level'] = $level; + if (!is_null($this->crashReporters)) { + $this->crashReporters->delegateReport($exception, $context); + } + } catch (\Throwable $e) { + // make sure we dont hard crash if logging fails + } + } + + public function logData(string $message, array $data, array $context = []): void { + $app = $context['app'] ?? 'no app in context'; + $level = $context['level'] ?? ILogger::ERROR; + + $minLevel = $this->getLogLevel($context); + + array_walk($context, [$this->normalizer, 'format']); + + try { + if ($level >= $minLevel) { + $data['message'] = $message; + if (!$this->logger instanceof IFileBased) { + $data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES); + } + $this->writeLog($app, $data, $level); + } + + $context['level'] = $level; + } catch (\Throwable $e) { + // make sure we dont hard crash if logging fails + } + } + + /** + * @param string $app + * @param string|array $entry + * @param int $level + */ + protected function writeLog(string $app, $entry, int $level) { + $this->logger->write($app, $entry, $level); + } + + public function getLogPath():string { + if ($this->logger instanceof IFileBased) { + return $this->logger->getLogFilePath(); + } + throw new \RuntimeException('Log implementation has no path'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/ErrorHandler.php b/docker/overlays/nextcloud/html/lib/private/Log/ErrorHandler.php new file mode 100644 index 0000000..e87da0b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/ErrorHandler.php @@ -0,0 +1,101 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Log; + +use OCP\ILogger; + +class ErrorHandler { + /** @var ILogger */ + private static $logger; + + /** + * remove password in URLs + * @param string $msg + * @return string + */ + protected static function removePassword($msg) { + return preg_replace('/\/\/(.*):(.*)@/', '//xxx:xxx@', $msg); + } + + public static function register($debug=false) { + $handler = new ErrorHandler(); + + if ($debug) { + set_error_handler([$handler, 'onAll'], E_ALL); + if (\OC::$CLI) { + set_exception_handler(['OC_Template', 'printExceptionErrorPage']); + } + } else { + set_error_handler([$handler, 'onError']); + } + register_shutdown_function([$handler, 'onShutdown']); + set_exception_handler([$handler, 'onException']); + } + + public static function setLogger(ILogger $logger) { + self::$logger = $logger; + } + + //Fatal errors handler + public static function onShutdown() { + $error = error_get_last(); + if ($error && self::$logger) { + //ob_end_clean(); + $msg = $error['message'] . ' at ' . $error['file'] . '#' . $error['line']; + self::$logger->critical(self::removePassword($msg), ['app' => 'PHP']); + } + } + + /** + * Uncaught exception handler + * + * @param \Exception $exception + */ + public static function onException($exception) { + $class = get_class($exception); + $msg = $exception->getMessage(); + $msg = "$class: $msg at " . $exception->getFile() . '#' . $exception->getLine(); + self::$logger->critical(self::removePassword($msg), ['app' => 'PHP']); + } + + //Recoverable errors handler + public static function onError($number, $message, $file, $line) { + if (error_reporting() === 0) { + return; + } + $msg = $message . ' at ' . $file . '#' . $line; + $e = new \Error(self::removePassword($msg)); + self::$logger->logException($e, ['app' => 'PHP']); + } + + //Recoverable handler which catch all errors, warnings and notices + public static function onAll($number, $message, $file, $line) { + $msg = $message . ' at ' . $file . '#' . $line; + $e = new \Error(self::removePassword($msg)); + self::$logger->logException($e, ['app' => 'PHP', 'level' => 0]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/Errorlog.php b/docker/overlays/nextcloud/html/lib/private/Log/Errorlog.php new file mode 100644 index 0000000..ebcb73b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/Errorlog.php @@ -0,0 +1,41 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +namespace OC\Log; + +use OCP\Log\IWriter; + +class Errorlog implements IWriter { + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int $level + */ + public function write(string $app, $message, int $level) { + error_log('[owncloud]['.$app.']['.$level.'] '.$message); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/ExceptionSerializer.php b/docker/overlays/nextcloud/html/lib/private/Log/ExceptionSerializer.php new file mode 100644 index 0000000..a2bc196 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/ExceptionSerializer.php @@ -0,0 +1,189 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OC\Core\Controller\SetupController; +use OC\HintException; +use OC\Security\IdentityProof\Key; +use OC\Setup; + +class ExceptionSerializer { + public const methodsWithSensitiveParameters = [ + // Session/User + 'completeLogin', + 'login', + 'checkPassword', + 'checkPasswordNoLogging', + 'loginWithPassword', + 'updatePrivateKeyPassword', + 'validateUserPass', + 'loginWithToken', + '{closure}', + 'createSessionToken', + + // Provisioning + 'addUser', + + // TokenProvider + 'getToken', + 'isTokenPassword', + 'getPassword', + 'decryptPassword', + 'logClientIn', + 'generateToken', + 'validateToken', + + // TwoFactorAuth + 'solveChallenge', + 'verifyChallenge', + + // ICrypto + 'calculateHMAC', + 'encrypt', + 'decrypt', + + // LoginController + 'tryLogin', + 'confirmPassword', + + // LDAP + 'bind', + 'areCredentialsValid', + 'invokeLDAPMethod', + + // Encryption + 'storeKeyPair', + 'setupUser', + + // files_external: OCA\Files_External\MountConfig + 'getBackendStatus', + + // files_external: UserStoragesController + 'update', + ]; + + public const methodsWithSensitiveParametersByClass = [ + SetupController::class => [ + 'run', + 'display', + 'loadAutoConfig', + ], + Setup::class => [ + 'install' + ], + Key::class => [ + '__construct' + ], + ]; + + private function editTrace(array &$sensitiveValues, array $traceLine): array { + if (isset($traceLine['args'])) { + $sensitiveValues = array_merge($sensitiveValues, $traceLine['args']); + } + $traceLine['args'] = ['*** sensitive parameters replaced ***']; + return $traceLine; + } + + private function filterTrace(array $trace) { + $sensitiveValues = []; + $trace = array_map(function (array $traceLine) use (&$sensitiveValues) { + $className = $traceLine['class'] ?? ''; + if ($className && isset(self::methodsWithSensitiveParametersByClass[$className]) + && in_array($traceLine['function'], self::methodsWithSensitiveParametersByClass[$className], true)) { + return $this->editTrace($sensitiveValues, $traceLine); + } + foreach (self::methodsWithSensitiveParameters as $sensitiveMethod) { + if (strpos($traceLine['function'], $sensitiveMethod) !== false) { + return $this->editTrace($sensitiveValues, $traceLine); + } + } + return $traceLine; + }, $trace); + return array_map(function (array $traceLine) use ($sensitiveValues) { + if (isset($traceLine['args'])) { + $traceLine['args'] = $this->removeValuesFromArgs($traceLine['args'], $sensitiveValues); + } + return $traceLine; + }, $trace); + } + + private function removeValuesFromArgs($args, $values) { + foreach ($args as &$arg) { + if (in_array($arg, $values, true)) { + $arg = '*** sensitive parameter replaced ***'; + } elseif (is_array($arg)) { + $arg = $this->removeValuesFromArgs($arg, $values); + } + } + return $args; + } + + private function encodeTrace($trace) { + $filteredTrace = $this->filterTrace($trace); + return array_map(function (array $line) { + if (isset($line['args'])) { + $line['args'] = array_map([$this, 'encodeArg'], $line['args']); + } + return $line; + }, $filteredTrace); + } + + private function encodeArg($arg) { + if (is_object($arg)) { + $data = get_object_vars($arg); + $data['__class__'] = get_class($arg); + return array_map([$this, 'encodeArg'], $data); + } elseif (is_array($arg)) { + return array_map([$this, 'encodeArg'], $arg); + } else { + return $arg; + } + } + + public function serializeException(\Throwable $exception) { + $data = [ + 'Exception' => get_class($exception), + 'Message' => $exception->getMessage(), + 'Code' => $exception->getCode(), + 'Trace' => $this->encodeTrace($exception->getTrace()), + 'File' => $exception->getFile(), + 'Line' => $exception->getLine(), + ]; + + if ($exception instanceof HintException) { + $data['Hint'] = $exception->getHint(); + } + + if ($exception->getPrevious()) { + $data['Previous'] = $this->serializeException($exception->getPrevious()); + } + + return $data; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/File.php b/docker/overlays/nextcloud/html/lib/private/Log/File.php new file mode 100644 index 0000000..b2fb160 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/File.php @@ -0,0 +1,158 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author duritong + * @author Georg Ehrke + * @author J0WI + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Roland Tapken + * @author Thomas Müller + * @author Thomas Pulzer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Log; + +use OC\SystemConfig; +use OCP\ILogger; +use OCP\Log\IFileBased; +use OCP\Log\IWriter; + +/** + * logging utilities + * + * Log is saved at data/nextcloud.log (on default) + */ + +class File extends LogDetails implements IWriter, IFileBased { + /** @var string */ + protected $logFile; + /** @var int */ + protected $logFileMode; + /** @var SystemConfig */ + private $config; + + public function __construct(string $path, string $fallbackPath = '', SystemConfig $config) { + parent::__construct($config); + $this->logFile = $path; + if (!file_exists($this->logFile)) { + if ( + ( + !is_writable(dirname($this->logFile)) + || !touch($this->logFile) + ) + && $fallbackPath !== '' + ) { + $this->logFile = $fallbackPath; + } + } + $this->config = $config; + $this->logFileMode = $config->getValue('logfilemode', 0640); + } + + /** + * write a message in the log + * @param string $app + * @param string|array $message + * @param int $level + */ + public function write(string $app, $message, int $level) { + $entry = $this->logDetailsAsJSON($app, $message, $level); + $handle = @fopen($this->logFile, 'a'); + if ($this->logFileMode > 0 && is_file($this->logFile) && (fileperms($this->logFile) & 0777) != $this->logFileMode) { + @chmod($this->logFile, $this->logFileMode); + } + if ($handle) { + fwrite($handle, $entry."\n"); + fclose($handle); + } else { + // Fall back to error_log + error_log($entry); + } + if (php_sapi_name() === 'cli-server') { + if (!\is_string($message)) { + $message = json_encode($message); + } + error_log($message, 4); + } + } + + /** + * get entries from the log in reverse chronological order + * @param int $limit + * @param int $offset + * @return array + */ + public function getEntries(int $limit=50, int $offset=0):array { + $minLevel = $this->config->getValue("loglevel", ILogger::WARN); + $entries = []; + $handle = @fopen($this->logFile, 'rb'); + if ($handle) { + fseek($handle, 0, SEEK_END); + $pos = ftell($handle); + $line = ''; + $entriesCount = 0; + $lines = 0; + // Loop through each character of the file looking for new lines + while ($pos >= 0 && ($limit === null ||$entriesCount < $limit)) { + fseek($handle, $pos); + $ch = fgetc($handle); + if ($ch == "\n" || $pos == 0) { + if ($line != '') { + // Add the first character if at the start of the file, + // because it doesn't hit the else in the loop + if ($pos == 0) { + $line = $ch.$line; + } + $entry = json_decode($line); + // Add the line as an entry if it is passed the offset and is equal or above the log level + if ($entry->level >= $minLevel) { + $lines++; + if ($lines > $offset) { + $entries[] = $entry; + $entriesCount++; + } + } + $line = ''; + } + } else { + $line = $ch.$line; + } + $pos--; + } + fclose($handle); + } + return $entries; + } + + /** + * @return string + */ + public function getLogFilePath():string { + return $this->logFile; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/LogDetails.php b/docker/overlays/nextcloud/html/lib/private/Log/LogDetails.php new file mode 100644 index 0000000..1175b47 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/LogDetails.php @@ -0,0 +1,113 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OC\SystemConfig; + +abstract class LogDetails { + + /** @var SystemConfig */ + private $config; + + public function __construct(SystemConfig $config) { + $this->config = $config; + } + + public function logDetails(string $app, $message, int $level): array { + // default to ISO8601 + $format = $this->config->getValue('logdateformat', \DateTime::ATOM); + $logTimeZone = $this->config->getValue('logtimezone', 'UTC'); + try { + $timezone = new \DateTimeZone($logTimeZone); + } catch (\Exception $e) { + $timezone = new \DateTimeZone('UTC'); + } + $time = \DateTime::createFromFormat("U.u", number_format(microtime(true), 4, ".", "")); + if ($time === false) { + $time = new \DateTime(null, $timezone); + } else { + // apply timezone if $time is created from UNIX timestamp + $time->setTimezone($timezone); + } + $request = \OC::$server->getRequest(); + $reqId = $request->getId(); + $remoteAddr = $request->getRemoteAddress(); + // remove username/passwords from URLs before writing the to the log file + $time = $time->format($format); + $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; + $method = is_string($request->getMethod()) ? $request->getMethod() : '--'; + if ($this->config->getValue('installed', false)) { + $user = \OC_User::getUser() ? \OC_User::getUser() : '--'; + } else { + $user = '--'; + } + $userAgent = $request->getHeader('User-Agent'); + if ($userAgent === '') { + $userAgent = '--'; + } + $version = $this->config->getValue('version', ''); + $entry = compact( + 'reqId', + 'level', + 'time', + 'remoteAddr', + 'user', + 'app', + 'method', + 'url', + 'message', + 'userAgent', + 'version' + ); + + if (is_array($message) && !array_key_exists('Exception', $message)) { + // Exception messages should stay as they are, + // anything else modern is split to 'message' (string) and + // data (array) fields + $shortMessage = $message['message'] ?? '(no message provided)'; + $entry['data'] = $message; + $entry['message'] = $shortMessage; + } + + return $entry; + } + + public function logDetailsAsJSON(string $app, $message, int $level): string { + $entry = $this->logDetails($app, $message, $level); + // PHP's json_encode only accept proper UTF-8 strings, loop over all + // elements to ensure that they are properly UTF-8 compliant or convert + // them manually. + foreach ($entry as $key => $value) { + if (is_string($value)) { + $testEncode = json_encode($value, JSON_UNESCAPED_SLASHES); + if ($testEncode === false) { + $entry[$key] = utf8_encode($value); + } + } + } + return json_encode($entry, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/LogFactory.php b/docker/overlays/nextcloud/html/lib/private/Log/LogFactory.php new file mode 100644 index 0000000..7064fe3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/LogFactory.php @@ -0,0 +1,82 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Johannes Ernst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OC\Log; +use OC\SystemConfig; +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\Log\ILogFactory; +use OCP\Log\IWriter; + +class LogFactory implements ILogFactory { + /** @var IServerContainer */ + private $c; + /** @var SystemConfig */ + private $systemConfig; + + public function __construct(IServerContainer $c, SystemConfig $systemConfig) { + $this->c = $c; + $this->systemConfig = $systemConfig; + } + + /** + * @throws \OCP\AppFramework\QueryException + */ + public function get(string $type):IWriter { + switch (strtolower($type)) { + case 'errorlog': + return new Errorlog(); + case 'syslog': + return $this->c->resolve(Syslog::class); + case 'systemd': + return $this->c->resolve(Systemdlog::class); + case 'file': + return $this->buildLogFile(); + + // Backwards compatibility for old and fallback for unknown log types + case 'owncloud': + case 'nextcloud': + default: + return $this->buildLogFile(); + } + } + + public function getCustomLogger(string $path):ILogger { + $log = $this->buildLogFile($path); + return new Log($log, $this->systemConfig); + } + + protected function buildLogFile(string $logFile = ''):File { + $defaultLogFile = $this->systemConfig->getValue('datadirectory', \OC::$SERVERROOT.'/data').'/nextcloud.log'; + if ($logFile === '') { + $logFile = $this->systemConfig->getValue('logfile', $defaultLogFile); + } + $fallback = $defaultLogFile !== $logFile ? $defaultLogFile : ''; + + return new File($logFile, $fallback, $this->systemConfig); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/PsrLoggerAdapter.php b/docker/overlays/nextcloud/html/lib/private/Log/PsrLoggerAdapter.php new file mode 100644 index 0000000..c488441 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/PsrLoggerAdapter.php @@ -0,0 +1,263 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OCP\ILogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Throwable; +use function array_key_exists; +use function array_merge; + +final class PsrLoggerAdapter implements LoggerInterface { + + /** @var ILogger */ + private $logger; + + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + private function containsThrowable(array $context): bool { + return array_key_exists('exception', $context) && $context['exception'] instanceof Throwable; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function emergency($message, array $context = []): void { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::FATAL, + ], + $context + )); + } else { + $this->logger->emergency($message, $context); + } + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::ERROR, + ], + $context + )); + } else { + $this->logger->alert($message, $context); + } + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::ERROR, + ], + $context + )); + } else { + $this->logger->critical($message, $context); + } + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::ERROR, + ], + $context + )); + } else { + $this->logger->error($message, $context); + } + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::WARN, + ], + $context + )); + } else { + $this->logger->warning($message, $context); + } + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::INFO, + ], + $context + )); + } else { + $this->logger->notice($message, $context); + } + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::INFO, + ], + $context + )); + } else { + $this->logger->info($message, $context); + } + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = []) { + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => ILogger::DEBUG, + ], + $context + )); + } else { + $this->logger->debug($message, $context); + } + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws InvalidArgumentException + */ + public function log($level, $message, array $context = []) { + if (!is_int($level) || $level < ILogger::DEBUG || $level > ILogger::FATAL) { + throw new InvalidArgumentException('Nextcloud allows only integer log levels'); + } + if ($this->containsThrowable($context)) { + $this->logger->logException($context['exception'], array_merge( + [ + 'message' => $message, + 'level' => $level, + ], + $context + )); + } else { + $this->logger->log($level, $message, $context); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/Rotate.php b/docker/overlays/nextcloud/html/lib/private/Log/Rotate.php new file mode 100644 index 0000000..76d026b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/Rotate.php @@ -0,0 +1,51 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Log; + +use OCP\Log\RotationTrait; + +/** + * This rotates the current logfile to a new name, this way the total log usage + * will stay limited and older entries are available for a while longer. + * For more professional log management set the 'logfile' config to a different + * location and manage that with your own tools. + */ +class Rotate extends \OC\BackgroundJob\Job { + use RotationTrait; + + public function run($dummy) { + $systemConfig = \OC::$server->getSystemConfig(); + $this->filePath = $systemConfig->getValue('logfile', $systemConfig->getValue('datadirectory', \OC::$SERVERROOT . '/data') . '/nextcloud.log'); + + $this->maxSize = \OC::$server->getConfig()->getSystemValue('log_rotate_size', 100 * 1024 * 1024); + if ($this->shouldRotateBySize()) { + $rotatedFile = $this->rotate(); + $msg = 'Log file "'.$this->filePath.'" was over '.$this->maxSize.' bytes, moved to "'.$rotatedFile.'"'; + \OC::$server->getLogger()->warning($msg, ['app' => Rotate::class]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/Syslog.php b/docker/overlays/nextcloud/html/lib/private/Log/Syslog.php new file mode 100644 index 0000000..02cffe0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/Syslog.php @@ -0,0 +1,62 @@ + + * @author Bart Visscher + * @author Julius Härtl + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Log; + +use OC\SystemConfig; +use OCP\ILogger; +use OCP\Log\IWriter; + +class Syslog extends LogDetails implements IWriter { + protected $levels = [ + ILogger::DEBUG => LOG_DEBUG, + ILogger::INFO => LOG_INFO, + ILogger::WARN => LOG_WARNING, + ILogger::ERROR => LOG_ERR, + ILogger::FATAL => LOG_CRIT, + ]; + + public function __construct(SystemConfig $config) { + parent::__construct($config); + openlog($config->getValue('syslog_tag', 'Nextcloud'), LOG_PID | LOG_CONS, LOG_USER); + } + + public function __destruct() { + closelog(); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int $level + */ + public function write(string $app, $message, int $level) { + $syslog_level = $this->levels[$level]; + syslog($syslog_level, $this->logDetailsAsJSON($app, $message, $level)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Log/Systemdlog.php b/docker/overlays/nextcloud/html/lib/private/Log/Systemdlog.php new file mode 100644 index 0000000..979befb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Log/Systemdlog.php @@ -0,0 +1,82 @@ + + * @author Johannes Ernst + * @author Julius Härtl + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OC\HintException; +use OC\SystemConfig; +use OCP\ILogger; +use OCP\Log\IWriter; + +// The following fields are understood by systemd/journald, see +// man systemd.journal-fields. All are optional: +// MESSAGE= +// The human-readable message string for this entry. +// MESSAGE_ID= +// A 128-bit message identifier ID +// PRIORITY= +// A priority value between 0 ("emerg") and 7 ("debug") +// CODE_FILE=, CODE_LINE=, CODE_FUNC= +// The code location generating this message, if known +// ERRNO= +// The low-level Unix error number causing this entry, if any. +// SYSLOG_FACILITY=, SYSLOG_IDENTIFIER=, SYSLOG_PID= +// Syslog compatibility fields + +class Systemdlog extends LogDetails implements IWriter { + protected $levels = [ + ILogger::DEBUG => 7, + ILogger::INFO => 6, + ILogger::WARN => 4, + ILogger::ERROR => 3, + ILogger::FATAL => 2, + ]; + + protected $syslogId; + + public function __construct(SystemConfig $config) { + parent::__construct($config); + if (!function_exists('sd_journal_send')) { + throw new HintException( + 'PHP extension php-systemd is not available.', + 'Please install and enable PHP extension systemd if you wish to log to the Systemd journal.'); + } + $this->syslogId = $config->getValue('syslog_tag', 'Nextcloud'); + } + + /** + * Write a message to the log. + * @param string $app + * @param string $message + * @param int $level + */ + public function write(string $app, $message, int $level) { + $journal_level = $this->levels[$level]; + sd_journal_send('PRIORITY='.$journal_level, + 'SYSLOG_IDENTIFIER='.$this->syslogId, + 'MESSAGE=' . $this->logDetailsAsJSON($app, $message, $level)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Mail/Attachment.php b/docker/overlays/nextcloud/html/lib/private/Mail/Attachment.php new file mode 100644 index 0000000..1f88c87 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Mail/Attachment.php @@ -0,0 +1,83 @@ + + * + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Mail; + +use OCP\Mail\IAttachment; + +/** + * Class Attachment + * + * @package OC\Mail + * @since 13.0.0 + */ +class Attachment implements IAttachment { + + /** @var \Swift_Mime_Attachment */ + protected $swiftAttachment; + + public function __construct(\Swift_Mime_Attachment $attachment) { + $this->swiftAttachment = $attachment; + } + + /** + * @param string $filename + * @return $this + * @since 13.0.0 + */ + public function setFilename(string $filename): IAttachment { + $this->swiftAttachment->setFilename($filename); + return $this; + } + + /** + * @param string $contentType + * @return $this + * @since 13.0.0 + */ + public function setContentType(string $contentType): IAttachment { + $this->swiftAttachment->setContentType($contentType); + return $this; + } + + /** + * @param string $body + * @return $this + * @since 13.0.0 + */ + public function setBody(string $body): IAttachment { + $this->swiftAttachment->setBody($body); + return $this; + } + + /** + * @return \Swift_Mime_Attachment + */ + public function getSwiftAttachment(): \Swift_Mime_Attachment { + return $this->swiftAttachment; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Mail/EMailTemplate.php b/docker/overlays/nextcloud/html/lib/private/Mail/EMailTemplate.php new file mode 100644 index 0000000..ade4390 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Mail/EMailTemplate.php @@ -0,0 +1,675 @@ + + * @copyright 2017, Lukas Reschke + * + * @author Bjoern Schiessle + * @author brad2014 + * @author Brad Rubenstein + * @author Christoph Wurst + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author Julius Härtl + * @author Liam JACK + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Simon Spannagel + * @author Tomasz Paluszkiewicz + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Mail; + +use OCP\Defaults; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use OCP\Mail\IEMailTemplate; + +/** + * Class EMailTemplate + * + * addBodyText and addBodyButtonGroup automatically opens the body + * addFooter, renderHtml, renderText automatically closes the body and the HTML if opened + * + * @package OC\Mail + */ +class EMailTemplate implements IEMailTemplate { + /** @var Defaults */ + protected $themingDefaults; + /** @var IURLGenerator */ + protected $urlGenerator; + /** @var IFactory */ + protected $l10nFactory; + /** @var string */ + protected $emailId; + /** @var array */ + protected $data; + + /** @var string */ + protected $subject = ''; + /** @var string */ + protected $htmlBody = ''; + /** @var string */ + protected $plainBody = ''; + /** @var bool indicated if the footer is added */ + protected $headerAdded = false; + /** @var bool indicated if the body is already opened */ + protected $bodyOpened = false; + /** @var bool indicated if there is a list open in the body */ + protected $bodyListOpened = false; + /** @var bool indicated if the footer is added */ + protected $footerAdded = false; + + protected $head = << + + + + + + + + + + + + + +
+
+EOF; + + protected $tail = << +
+ +
                                                           
+ + + +EOF; + + protected $header = << + + + + + + + + +
+ + + +
+ +
+ + +
+
+ + + + + + + + + +
 
+EOF; + + protected $heading = << + + + +

%s

+ + + + + + + + + + +
 
+EOF; + + protected $bodyBegin = << + + + + + + + + + + +
+EOF; + + protected $bodyText = << +
+ + + + + +
+

%s

+
+
+EOF; + + // note: listBegin (like bodyBegin) is not processed through sprintf, so "%" is not escaped as "%%". (bug #12151) + protected $listBegin = << + + + + +EOF; + + protected $listItem = << + + + + +EOF; + + protected $listEnd = << + + + +
+

%s

+
+

%s

+
+EOF; + + protected $buttonGroup = << + + +   + + + + + + + + + +
+ + + + + +
+
+ + + + +
+ + + + +
+ %7\$s +
+
+ + + + +
+ + + + +
+ %9\$s +
+
+
+
+
+EOF; + + protected $button = << + + +   + + + + + + + + + +
+ + + + + +
+
+ + + + +
+ + + + +
+ %7\$s +
+
+
+
+
+EOF; + + protected $bodyEnd = << + + + + + + +EOF; + + protected $footer = << + + +   + + + + + + + + +EOF; + + public function __construct(Defaults $themingDefaults, + IURLGenerator $urlGenerator, + IFactory $l10nFactory, + $emailId, + array $data) { + $this->themingDefaults = $themingDefaults; + $this->urlGenerator = $urlGenerator; + $this->l10nFactory = $l10nFactory; + $this->htmlBody .= $this->head; + $this->emailId = $emailId; + $this->data = $data; + } + + /** + * Sets the subject of the email + * + * @param string $subject + */ + public function setSubject(string $subject) { + $this->subject = $subject; + } + + /** + * Adds a header to the email + */ + public function addHeader() { + if ($this->headerAdded) { + return; + } + $this->headerAdded = true; + + $logoUrl = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo(false)); + $this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getColorPrimary(), $logoUrl, $this->themingDefaults->getName()]); + } + + /** + * Adds a heading to the email + * + * @param string $title + * @param string|bool $plainTitle Title that is used in the plain text email + * if empty the $title is used, if false none will be used + */ + public function addHeading(string $title, $plainTitle = '') { + if ($this->footerAdded) { + return; + } + if ($plainTitle === '') { + $plainTitle = $title; + } + + $this->htmlBody .= vsprintf($this->heading, [htmlspecialchars($title)]); + if ($plainTitle !== false) { + $this->plainBody .= $plainTitle . PHP_EOL . PHP_EOL; + } + } + + /** + * Open the HTML body when it is not already + */ + protected function ensureBodyIsOpened() { + if ($this->bodyOpened) { + return; + } + + $this->htmlBody .= $this->bodyBegin; + $this->bodyOpened = true; + } + + /** + * Adds a paragraph to the body of the email + * + * @param string $text Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email + * @param string|bool $plainText Text that is used in the plain text email + * if empty the $text is used, if false none will be used + */ + public function addBodyText(string $text, $plainText = '') { + if ($this->footerAdded) { + return; + } + if ($plainText === '') { + $plainText = $text; + $text = htmlspecialchars($text); + } + + $this->ensureBodyListClosed(); + $this->ensureBodyIsOpened(); + + $this->htmlBody .= vsprintf($this->bodyText, [$text]); + if ($plainText !== false) { + $this->plainBody .= $plainText . PHP_EOL . PHP_EOL; + } + } + + /** + * Adds a list item to the body of the email + * + * @param string $text Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email + * @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email + * @param string $icon Absolute path, must be 16*16 pixels + * @param string|bool $plainText Text that is used in the plain text email + * if empty or true the $text is used, if false none will be used + * @param string|bool $plainMetaInfo Meta info that is used in the plain text email + * if empty or true the $metaInfo is used, if false none will be used + * @param integer plainIndent If > 0, Indent plainText by this amount. + * @since 12.0.0 + */ + public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = '', $plainIndent = 0) { + $this->ensureBodyListOpened(); + + if ($plainText === '' || $plainText === true) { + $plainText = $text; + $text = htmlspecialchars($text); + $text = str_replace("\n", "
", $text); // convert newlines to HTML breaks + } + if ($plainMetaInfo === '' || $plainMetaInfo === true) { + $plainMetaInfo = $metaInfo; + $metaInfo = htmlspecialchars($metaInfo); + } + + $htmlText = $text; + if ($metaInfo) { + $htmlText = '' . $metaInfo . '
' . $htmlText; + } + if ($icon !== '') { + $icon = '•'; + } else { + $icon = '•'; + } + $this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]); + if ($plainText !== false) { + if ($plainIndent === 0) { + /* + * If plainIndent is not set by caller, this is the old NC17 layout code. + */ + $this->plainBody .= ' * ' . $plainText; + if ($plainMetaInfo !== false) { + $this->plainBody .= ' (' . $plainMetaInfo . ')'; + } + $this->plainBody .= PHP_EOL; + } else { + /* + * Caller can set plainIndent > 0 to format plainText in tabular fashion. + * with plainMetaInfo in column 1, and plainText in column 2. + * The plainMetaInfo label is right justified in a field of width + * "plainIndent". Multilines after the first are indented plainIndent+1 + * (to account for space after label). Fixes: #12391 + */ + /** @var string $label */ + $label = ($plainMetaInfo !== false)? $plainMetaInfo : ''; + $this->plainBody .= sprintf("%${plainIndent}s %s\n", + $label, + str_replace("\n", "\n" . str_repeat(' ', $plainIndent+1), $plainText)); + } + } + } + + protected function ensureBodyListOpened() { + if ($this->bodyListOpened) { + return; + } + + $this->ensureBodyIsOpened(); + $this->bodyListOpened = true; + $this->htmlBody .= $this->listBegin; + } + + protected function ensureBodyListClosed() { + if (!$this->bodyListOpened) { + return; + } + + $this->bodyListOpened = false; + $this->htmlBody .= $this->listEnd; + } + + /** + * Adds a button group of two buttons to the body of the email + * + * @param string $textLeft Text of left button; Note: When $plainTextLeft falls back to this, HTML is automatically escaped in the HTML email + * @param string $urlLeft URL of left button + * @param string $textRight Text of right button; Note: When $plainTextRight falls back to this, HTML is automatically escaped in the HTML email + * @param string $urlRight URL of right button + * @param string $plainTextLeft Text of left button that is used in the plain text version - if unset the $textLeft is used + * @param string $plainTextRight Text of right button that is used in the plain text version - if unset the $textRight is used + */ + public function addBodyButtonGroup(string $textLeft, + string $urlLeft, + string $textRight, + string $urlRight, + string $plainTextLeft = '', + string $plainTextRight = '') { + if ($this->footerAdded) { + return; + } + if ($plainTextLeft === '') { + $plainTextLeft = $textLeft; + $textLeft = htmlspecialchars($textLeft); + } + + if ($plainTextRight === '') { + $plainTextRight = $textRight; + $textRight = htmlspecialchars($textRight); + } + + $this->ensureBodyIsOpened(); + $this->ensureBodyListClosed(); + + $color = $this->themingDefaults->getColorPrimary(); + $textColor = $this->themingDefaults->getTextColorPrimary(); + + $this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]); + $this->plainBody .= PHP_EOL . $plainTextLeft . ': ' . $urlLeft . PHP_EOL; + $this->plainBody .= $plainTextRight . ': ' . $urlRight . PHP_EOL . PHP_EOL; + } + + /** + * Adds a button to the body of the email + * + * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email + * @param string $url URL of button + * @param string $plainText Text of button in plain text version + * if empty the $text is used, if false none will be used + * + * @since 12.0.0 + */ + public function addBodyButton(string $text, string $url, $plainText = '') { + if ($this->footerAdded) { + return; + } + + $this->ensureBodyIsOpened(); + $this->ensureBodyListClosed(); + + if ($plainText === '') { + $plainText = $text; + $text = htmlspecialchars($text); + } + + $color = $this->themingDefaults->getColorPrimary(); + $textColor = $this->themingDefaults->getTextColorPrimary(); + $this->htmlBody .= vsprintf($this->button, [$color, $color, $url, $color, $textColor, $textColor, $text]); + + if ($plainText !== false) { + $this->plainBody .= $plainText . ': '; + } + + $this->plainBody .= $url . PHP_EOL; + } + + /** + * Close the HTML body when it is open + */ + protected function ensureBodyIsClosed() { + if (!$this->bodyOpened) { + return; + } + + $this->ensureBodyListClosed(); + + $this->htmlBody .= $this->bodyEnd; + $this->bodyOpened = false; + } + + /** + * Adds a logo and a text to the footer.
in the text will be replaced by new lines in the plain text email + * + * @param string $text If the text is empty the default "Name - Slogan
This is an automatically sent email" will be used + */ + public function addFooter(string $text = '', ?string $lang = null) { + if ($text === '') { + $l10n = $this->l10nFactory->get('lib', $lang); + $text = $this->themingDefaults->getName() . ' - ' . $this->themingDefaults->getSlogan($lang) . '
' . $l10n->t('This is an automatically sent email, please do not reply.'); + } + + if ($this->footerAdded) { + return; + } + $this->footerAdded = true; + + $this->ensureBodyIsClosed(); + + $this->htmlBody .= vsprintf($this->footer, [$text]); + $this->htmlBody .= $this->tail; + $this->plainBody .= PHP_EOL . '-- ' . PHP_EOL; + $this->plainBody .= str_replace('
', PHP_EOL, $text); + } + + /** + * Returns the rendered email subject as string + * + * @return string + */ + public function renderSubject(): string { + return $this->subject; + } + + /** + * Returns the rendered HTML email as string + * + * @return string + */ + public function renderHtml(): string { + if (!$this->footerAdded) { + $this->footerAdded = true; + $this->ensureBodyIsClosed(); + $this->htmlBody .= $this->tail; + } + return $this->htmlBody; + } + + /** + * Returns the rendered plain text email as string + * + * @return string + */ + public function renderText(): string { + if (!$this->footerAdded) { + $this->footerAdded = true; + $this->ensureBodyIsClosed(); + $this->htmlBody .= $this->tail; + } + return $this->plainBody; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Mail/Mailer.php b/docker/overlays/nextcloud/html/lib/private/Mail/Mailer.php new file mode 100644 index 0000000..fc8de9c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Mail/Mailer.php @@ -0,0 +1,324 @@ + + * @author Branko Kokanovic + * @author Carsten Wiedmann + * @author Christoph Wurst + * @author Jared Boone + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Tekhnee + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Mail; + +use Egulias\EmailValidator\EmailValidator; +use Egulias\EmailValidator\Validation\RFCValidation; +use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use OCP\Mail\IAttachment; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMailer; +use OCP\Mail\IMessage; +use OCP\Mail\Events\BeforeMessageSent; + +/** + * Class Mailer provides some basic functions to create a mail message that can be used in combination with + * \OC\Mail\Message. + * + * Example usage: + * + * $mailer = \OC::$server->getMailer(); + * $message = $mailer->createMessage(); + * $message->setSubject('Your Subject'); + * $message->setFrom(array('cloud@domain.org' => 'ownCloud Notifier'); + * $message->setTo(array('recipient@domain.org' => 'Recipient'); + * $message->setBody('The message text'); + * $mailer->send($message); + * + * This message can then be passed to send() of \OC\Mail\Mailer + * + * @package OC\Mail + */ +class Mailer implements IMailer { + /** @var \Swift_Mailer Cached mailer */ + private $instance = null; + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + /** @var Defaults */ + private $defaults; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IL10N */ + private $l10n; + /** @var IEventDispatcher */ + private $dispatcher; + /** @var IFactory */ + private $l10nFactory; + + /** + * @param IConfig $config + * @param ILogger $logger + * @param Defaults $defaults + * @param IURLGenerator $urlGenerator + * @param IL10N $l10n + * @param IEventDispatcher $dispatcher + */ + public function __construct(IConfig $config, + ILogger $logger, + Defaults $defaults, + IURLGenerator $urlGenerator, + IL10N $l10n, + IEventDispatcher $dispatcher, + IFactory $l10nFactory) { + $this->config = $config; + $this->logger = $logger; + $this->defaults = $defaults; + $this->urlGenerator = $urlGenerator; + $this->l10n = $l10n; + $this->dispatcher = $dispatcher; + $this->l10nFactory = $l10nFactory; + } + + /** + * Creates a new message object that can be passed to send() + * + * @return IMessage + */ + public function createMessage(): IMessage { + $plainTextOnly = $this->config->getSystemValue('mail_send_plaintext_only', false); + return new Message(new \Swift_Message(), $plainTextOnly); + } + + /** + * @param string|null $data + * @param string|null $filename + * @param string|null $contentType + * @return IAttachment + * @since 13.0.0 + */ + public function createAttachment($data = null, $filename = null, $contentType = null): IAttachment { + return new Attachment(new \Swift_Attachment($data, $filename, $contentType)); + } + + /** + * @param string $path + * @param string|null $contentType + * @return IAttachment + * @since 13.0.0 + */ + public function createAttachmentFromPath(string $path, $contentType = null): IAttachment { + return new Attachment(\Swift_Attachment::fromPath($path, $contentType)); + } + + /** + * Creates a new email template object + * + * @param string $emailId + * @param array $data + * @return IEMailTemplate + * @since 12.0.0 + */ + public function createEMailTemplate(string $emailId, array $data = []): IEMailTemplate { + $class = $this->config->getSystemValue('mail_template_class', ''); + + if ($class !== '' && class_exists($class) && is_a($class, EMailTemplate::class, true)) { + return new $class( + $this->defaults, + $this->urlGenerator, + $this->l10nFactory, + $emailId, + $data + ); + } + + return new EMailTemplate( + $this->defaults, + $this->urlGenerator, + $this->l10nFactory, + $emailId, + $data + ); + } + + /** + * Send the specified message. Also sets the from address to the value defined in config.php + * if no-one has been passed. + * + * @param IMessage|Message $message Message to send + * @return string[] Array with failed recipients. Be aware that this depends on the used mail backend and + * therefore should be considered + * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address + * has been supplied.) + */ + public function send(IMessage $message): array { + $debugMode = $this->config->getSystemValue('mail_smtpdebug', false); + + if (empty($message->getFrom())) { + $message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]); + } + + $failedRecipients = []; + + $mailer = $this->getInstance(); + + // Enable logger if debug mode is enabled + if ($debugMode) { + $mailLogger = new \Swift_Plugins_Loggers_ArrayLogger(); + $mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger)); + } + + + $this->dispatcher->dispatchTyped(new BeforeMessageSent($message)); + + $mailer->send($message->getSwiftMessage(), $failedRecipients); + + // Debugging logging + $logMessage = sprintf('Sent mail to "%s" with subject "%s"', print_r($message->getTo(), true), $message->getSubject()); + $this->logger->debug($logMessage, ['app' => 'core']); + if ($debugMode && isset($mailLogger)) { + $this->logger->debug($mailLogger->dump(), ['app' => 'core']); + } + + return $failedRecipients; + } + + /** + * Checks if an e-mail address is valid + * + * @param string $email Email address to be validated + * @return bool True if the mail address is valid, false otherwise + */ + public function validateMailAddress(string $email): bool { + $validator = new EmailValidator(); + $validation = new RFCValidation(); + + return $validator->isValid($this->convertEmail($email), $validation); + } + + /** + * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains + * + * FIXME: Remove this once SwiftMailer supports IDN + * + * @param string $email + * @return string Converted mail address if `idn_to_ascii` exists + */ + protected function convertEmail(string $email): string { + if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46') || strpos($email, '@') === false) { + return $email; + } + + list($name, $domain) = explode('@', $email, 2); + $domain = idn_to_ascii($domain, 0,INTL_IDNA_VARIANT_UTS46); + return $name.'@'.$domain; + } + + protected function getInstance(): \Swift_Mailer { + if (!is_null($this->instance)) { + return $this->instance; + } + + $transport = null; + + switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) { + case 'sendmail': + $transport = $this->getSendMailInstance(); + break; + case 'smtp': + default: + $transport = $this->getSmtpInstance(); + break; + } + + return new \Swift_Mailer($transport); + } + + /** + * Returns the SMTP transport + * + * @return \Swift_SmtpTransport + */ + protected function getSmtpInstance(): \Swift_SmtpTransport { + $transport = new \Swift_SmtpTransport(); + $transport->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10)); + $transport->setHost($this->config->getSystemValue('mail_smtphost', '127.0.0.1')); + $transport->setPort($this->config->getSystemValue('mail_smtpport', 25)); + if ($this->config->getSystemValue('mail_smtpauth', false)) { + $transport->setUsername($this->config->getSystemValue('mail_smtpname', '')); + $transport->setPassword($this->config->getSystemValue('mail_smtppassword', '')); + $transport->setAuthMode($this->config->getSystemValue('mail_smtpauthtype', 'LOGIN')); + } + $smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', ''); + if (!empty($smtpSecurity)) { + $transport->setEncryption($smtpSecurity); + } + $streamingOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []); + if (is_array($streamingOptions) && !empty($streamingOptions)) { + $transport->setStreamOptions($streamingOptions); + } + + return $transport; + } + + /** + * Returns the sendmail transport + * + * @return \Swift_SendmailTransport + */ + protected function getSendMailInstance(): \Swift_SendmailTransport { + switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) { + case 'qmail': + $binaryPath = '/var/qmail/bin/sendmail'; + break; + default: + $sendmail = \OC_Helper::findBinaryPath('sendmail'); + if ($sendmail === null) { + $sendmail = '/usr/sbin/sendmail'; + } + $binaryPath = $sendmail; + break; + } + + switch ($this->config->getSystemValue('mail_sendmailmode', 'smtp')) { + case 'pipe': + $binaryParam = ' -t'; + break; + default: + $binaryParam = ' -bs'; + break; + } + + return new \Swift_SendmailTransport($binaryPath . $binaryParam); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Mail/Message.php b/docker/overlays/nextcloud/html/lib/private/Mail/Message.php new file mode 100644 index 0000000..bd7c01b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Mail/Message.php @@ -0,0 +1,301 @@ + + * @author Christoph Wurst + * @author Jared Boone + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Mail; + +use OCP\Mail\IAttachment; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMessage; +use Swift_Message; + +/** + * Class Message provides a wrapper around SwiftMail + * + * @package OC\Mail + */ +class Message implements IMessage { + /** @var Swift_Message */ + private $swiftMessage; + /** @var bool */ + private $plainTextOnly; + + public function __construct(Swift_Message $swiftMessage, bool $plainTextOnly) { + $this->swiftMessage = $swiftMessage; + $this->plainTextOnly = $plainTextOnly; + } + + /** + * @param IAttachment $attachment + * @return $this + * @since 13.0.0 + */ + public function attach(IAttachment $attachment): IMessage { + /** @var Attachment $attachment */ + $this->swiftMessage->attach($attachment->getSwiftAttachment()); + return $this; + } + + /** + * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains + * FIXME: Remove this once SwiftMailer supports IDN + * + * @param array $addresses Array of mail addresses, key will get converted + * @return array Converted addresses if `idn_to_ascii` exists + */ + protected function convertAddresses(array $addresses): array { + if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46')) { + return $addresses; + } + + $convertedAddresses = []; + + foreach ($addresses as $email => $readableName) { + if (!is_numeric($email)) { + list($name, $domain) = explode('@', $email, 2); + $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); + $convertedAddresses[$name.'@'.$domain] = $readableName; + } else { + list($name, $domain) = explode('@', $readableName, 2); + $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); + $convertedAddresses[$email] = $name.'@'.$domain; + } + } + + return $convertedAddresses; + } + + /** + * Set the from address of this message. + * + * If no "From" address is used \OC\Mail\Mailer will use mail_from_address and mail_domain from config.php + * + * @param array $addresses Example: array('sender@domain.org', 'other@domain.org' => 'A name') + * @return $this + */ + public function setFrom(array $addresses): IMessage { + $addresses = $this->convertAddresses($addresses); + + $this->swiftMessage->setFrom($addresses); + return $this; + } + + /** + * Get the from address of this message. + * + * @return array + */ + public function getFrom(): array { + return $this->swiftMessage->getFrom() ?? []; + } + + /** + * Set the Reply-To address of this message + * + * @param array $addresses + * @return $this + */ + public function setReplyTo(array $addresses): IMessage { + $addresses = $this->convertAddresses($addresses); + + $this->swiftMessage->setReplyTo($addresses); + return $this; + } + + /** + * Returns the Reply-To address of this message + * + * @return string + */ + public function getReplyTo(): string { + return $this->swiftMessage->getReplyTo(); + } + + /** + * Set the to addresses of this message. + * + * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') + * @return $this + */ + public function setTo(array $recipients): IMessage { + $recipients = $this->convertAddresses($recipients); + + $this->swiftMessage->setTo($recipients); + return $this; + } + + /** + * Get the to address of this message. + * + * @return array + */ + public function getTo(): array { + return $this->swiftMessage->getTo() ?? []; + } + + /** + * Set the CC recipients of this message. + * + * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') + * @return $this + */ + public function setCc(array $recipients): IMessage { + $recipients = $this->convertAddresses($recipients); + + $this->swiftMessage->setCc($recipients); + return $this; + } + + /** + * Get the cc address of this message. + * + * @return array + */ + public function getCc(): array { + return $this->swiftMessage->getCc() ?? []; + } + + /** + * Set the BCC recipients of this message. + * + * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') + * @return $this + */ + public function setBcc(array $recipients): IMessage { + $recipients = $this->convertAddresses($recipients); + + $this->swiftMessage->setBcc($recipients); + return $this; + } + + /** + * Get the Bcc address of this message. + * + * @return array + */ + public function getBcc(): array { + return $this->swiftMessage->getBcc() ?? []; + } + + /** + * Set the subject of this message. + * + * @param string $subject + * @return IMessage + */ + public function setSubject(string $subject): IMessage { + $this->swiftMessage->setSubject($subject); + return $this; + } + + /** + * Get the from subject of this message. + * + * @return string + */ + public function getSubject(): string { + return $this->swiftMessage->getSubject(); + } + + /** + * Set the plain-text body of this message. + * + * @param string $body + * @return $this + */ + public function setPlainBody(string $body): IMessage { + $this->swiftMessage->setBody($body); + return $this; + } + + /** + * Get the plain body of this message. + * + * @return string + */ + public function getPlainBody(): string { + return $this->swiftMessage->getBody(); + } + + /** + * Set the HTML body of this message. Consider also sending a plain-text body instead of only an HTML one. + * + * @param string $body + * @return $this + */ + public function setHtmlBody($body) { + if (!$this->plainTextOnly) { + $this->swiftMessage->addPart($body, 'text/html'); + } + return $this; + } + + /** + * Get's the underlying SwiftMessage + * @param Swift_Message $swiftMessage + */ + public function setSwiftMessage(Swift_Message $swiftMessage): void { + $this->swiftMessage = $swiftMessage; + } + + /** + * Get's the underlying SwiftMessage + * @return Swift_Message + */ + public function getSwiftMessage(): Swift_Message { + return $this->swiftMessage; + } + + /** + * @param string $body + * @param string $contentType + * @return $this + */ + public function setBody($body, $contentType) { + if (!$this->plainTextOnly || $contentType !== 'text/html') { + $this->swiftMessage->setBody($body, $contentType); + } + return $this; + } + + /** + * @param IEMailTemplate $emailTemplate + * @return $this + */ + public function useTemplate(IEMailTemplate $emailTemplate): IMessage { + $this->setSubject($emailTemplate->renderSubject()); + $this->setPlainBody($emailTemplate->renderText()); + if (!$this->plainTextOnly) { + $this->setHtmlBody($emailTemplate->renderHtml()); + } + return $this; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/APCu.php b/docker/overlays/nextcloud/html/lib/private/Memcache/APCu.php new file mode 100644 index 0000000..ed3c81b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/APCu.php @@ -0,0 +1,171 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\IMemcache; + +class APCu extends Cache implements IMemcache { + use CASTrait { + cas as casEmulated; + } + + use CADTrait; + + public function get($key) { + $result = apcu_fetch($this->getPrefix() . $key, $success); + if (!$success) { + return null; + } + return $result; + } + + public function set($key, $value, $ttl = 0) { + return apcu_store($this->getPrefix() . $key, $value, $ttl); + } + + public function hasKey($key) { + return apcu_exists($this->getPrefix() . $key); + } + + public function remove($key) { + return apcu_delete($this->getPrefix() . $key); + } + + public function clear($prefix = '') { + $ns = $this->getPrefix() . $prefix; + $ns = preg_quote($ns, '/'); + if (class_exists('\APCIterator')) { + $iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY); + } else { + $iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); + } + return apcu_delete($iter); + } + + /** + * Set a value in the cache if it's not already stored + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + */ + public function add($key, $value, $ttl = 0) { + return apcu_add($this->getPrefix() . $key, $value, $ttl); + } + + /** + * Increase a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function inc($key, $step = 1) { + $this->add($key, 0); + /** + * TODO - hack around a PHP 7 specific issue in APCu + * + * on PHP 7 the apcu_inc method on a non-existing object will increment + * "0" and result in "1" as value - therefore we check for existence + * first + * + * on PHP 5.6 this is not the case + * + * see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221 + * for details + */ + return apcu_exists($this->getPrefix() . $key) + ? apcu_inc($this->getPrefix() . $key, $step) + : false; + } + + /** + * Decrease a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function dec($key, $step = 1) { + /** + * TODO - hack around a PHP 7 specific issue in APCu + * + * on PHP 7 the apcu_dec method on a non-existing object will decrement + * "0" and result in "-1" as value - therefore we check for existence + * first + * + * on PHP 5.6 this is not the case + * + * see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221 + * for details + */ + return apcu_exists($this->getPrefix() . $key) + ? apcu_dec($this->getPrefix() . $key, $step) + : false; + } + + /** + * Compare and set + * + * @param string $key + * @param mixed $old + * @param mixed $new + * @return bool + */ + public function cas($key, $old, $new) { + // apc only does cas for ints + if (is_int($old) and is_int($new)) { + return apcu_cas($this->getPrefix() . $key, $old, $new); + } else { + return $this->casEmulated($key, $old, $new); + } + } + + /** + * @return bool + */ + public static function isAvailable() { + if (!extension_loaded('apcu')) { + return false; + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { + return false; + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) { + return false; + } elseif ( + version_compare(phpversion('apc') ?: '0.0.0', '4.0.6') === -1 && + version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1 + ) { + return false; + } else { + return true; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/ArrayCache.php b/docker/overlays/nextcloud/html/lib/private/Memcache/ArrayCache.php new file mode 100644 index 0000000..8c91d9f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/ArrayCache.php @@ -0,0 +1,160 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +use OCP\IMemcache; + +class ArrayCache extends Cache implements IMemcache { + /** @var array Array with the cached data */ + protected $cachedData = []; + + use CADTrait; + + /** + * {@inheritDoc} + */ + public function get($key) { + if ($this->hasKey($key)) { + return $this->cachedData[$key]; + } + return null; + } + + /** + * {@inheritDoc} + */ + public function set($key, $value, $ttl = 0) { + $this->cachedData[$key] = $value; + return true; + } + + /** + * {@inheritDoc} + */ + public function hasKey($key) { + return isset($this->cachedData[$key]); + } + + /** + * {@inheritDoc} + */ + public function remove($key) { + unset($this->cachedData[$key]); + return true; + } + + /** + * {@inheritDoc} + */ + public function clear($prefix = '') { + if ($prefix === '') { + $this->cachedData = []; + return true; + } + + foreach ($this->cachedData as $key => $value) { + if (strpos($key, $prefix) === 0) { + $this->remove($key); + } + } + return true; + } + + /** + * Set a value in the cache if it's not already stored + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + */ + public function add($key, $value, $ttl = 0) { + // since this cache is not shared race conditions aren't an issue + if ($this->hasKey($key)) { + return false; + } else { + return $this->set($key, $value, $ttl); + } + } + + /** + * Increase a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function inc($key, $step = 1) { + $oldValue = $this->get($key); + if (is_int($oldValue)) { + $this->set($key, $oldValue + $step); + return $oldValue + $step; + } else { + $success = $this->add($key, $step); + return $success ? $step : false; + } + } + + /** + * Decrease a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function dec($key, $step = 1) { + $oldValue = $this->get($key); + if (is_int($oldValue)) { + $this->set($key, $oldValue - $step); + return $oldValue - $step; + } else { + return false; + } + } + + /** + * Compare and set + * + * @param string $key + * @param mixed $old + * @param mixed $new + * @return bool + */ + public function cas($key, $old, $new) { + if ($this->get($key) === $old) { + return $this->set($key, $new); + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + public static function isAvailable() { + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/CADTrait.php b/docker/overlays/nextcloud/html/lib/private/Memcache/CADTrait.php new file mode 100644 index 0000000..4c02c33 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/CADTrait.php @@ -0,0 +1,54 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +trait CADTrait { + abstract public function get($key); + + abstract public function remove($key); + + abstract public function add($key, $value, $ttl = 0); + + /** + * Compare and delete + * + * @param string $key + * @param mixed $old + * @return bool + */ + public function cad($key, $old) { + //no native cas, emulate with locking + if ($this->add($key . '_lock', true)) { + if ($this->get($key) === $old) { + $this->remove($key); + $this->remove($key . '_lock'); + return true; + } else { + $this->remove($key . '_lock'); + return false; + } + } else { + return false; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/CASTrait.php b/docker/overlays/nextcloud/html/lib/private/Memcache/CASTrait.php new file mode 100644 index 0000000..163afa2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/CASTrait.php @@ -0,0 +1,57 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +trait CASTrait { + abstract public function get($key); + + abstract public function set($key, $value, $ttl = 0); + + abstract public function remove($key); + + abstract public function add($key, $value, $ttl = 0); + + /** + * Compare and set + * + * @param string $key + * @param mixed $old + * @param mixed $new + * @return bool + */ + public function cas($key, $old, $new) { + //no native cas, emulate with locking + if ($this->add($key . '_lock', true)) { + if ($this->get($key) === $old) { + $this->set($key, $new); + $this->remove($key . '_lock'); + return true; + } else { + $this->remove($key . '_lock'); + return false; + } + } else { + return false; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/Cache.php b/docker/overlays/nextcloud/html/lib/private/Memcache/Cache.php new file mode 100644 index 0000000..fe141b7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/Cache.php @@ -0,0 +1,97 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +abstract class Cache implements \ArrayAccess, \OCP\ICache { + /** + * @var string $prefix + */ + protected $prefix; + + /** + * @param string $prefix + */ + public function __construct($prefix = '') { + $this->prefix = $prefix; + } + + /** + * @return string Prefix used for caching purposes + */ + public function getPrefix() { + return $this->prefix; + } + + /** + * @param string $key + * @return mixed + */ + abstract public function get($key); + + /** + * @param string $key + * @param mixed $value + * @param int $ttl + * @return mixed + */ + abstract public function set($key, $value, $ttl = 0); + + /** + * @param string $key + * @return mixed + */ + abstract public function hasKey($key); + + /** + * @param string $key + * @return mixed + */ + abstract public function remove($key); + + /** + * @param string $prefix + * @return mixed + */ + abstract public function clear($prefix = ''); + + //implement the ArrayAccess interface + + public function offsetExists($offset) { + return $this->hasKey($offset); + } + + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + public function offsetGet($offset) { + return $this->get($offset); + } + + public function offsetUnset($offset) { + $this->remove($offset); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/Factory.php b/docker/overlays/nextcloud/html/lib/private/Memcache/Factory.php new file mode 100644 index 0000000..030769b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/Factory.php @@ -0,0 +1,194 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Markus Goetz + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\ILogger; +use OCP\IMemcache; + +class Factory implements ICacheFactory { + public const NULL_CACHE = NullCache::class; + + /** + * @var string $globalPrefix + */ + private $globalPrefix; + + /** + * @var ILogger $logger + */ + private $logger; + + /** + * @var string $localCacheClass + */ + private $localCacheClass; + + /** + * @var string $distributedCacheClass + */ + private $distributedCacheClass; + + /** + * @var string $lockingCacheClass + */ + private $lockingCacheClass; + + /** + * @param string $globalPrefix + * @param ILogger $logger + * @param string|null $localCacheClass + * @param string|null $distributedCacheClass + * @param string|null $lockingCacheClass + */ + public function __construct(string $globalPrefix, ILogger $logger, + $localCacheClass = null, $distributedCacheClass = null, $lockingCacheClass = null) { + $this->logger = $logger; + $this->globalPrefix = $globalPrefix; + + if (!$localCacheClass) { + $localCacheClass = self::NULL_CACHE; + } + if (!$distributedCacheClass) { + $distributedCacheClass = $localCacheClass; + } + + $missingCacheMessage = 'Memcache {class} not available for {use} cache'; + $missingCacheHint = 'Is the matching PHP module installed and enabled?'; + if (!class_exists($localCacheClass) || !$localCacheClass::isAvailable()) { + if (\OC::$CLI && !defined('PHPUNIT_RUN')) { + // CLI should not hard-fail on broken memcache + $this->logger->info($missingCacheMessage, [ + 'class' => $localCacheClass, + 'use' => 'local', + 'app' => 'cli' + ]); + $localCacheClass = self::NULL_CACHE; + } else { + throw new \OC\HintException(strtr($missingCacheMessage, [ + '{class}' => $localCacheClass, '{use}' => 'local' + ]), $missingCacheHint); + } + } + if (!class_exists($distributedCacheClass) || !$distributedCacheClass::isAvailable()) { + if (\OC::$CLI && !defined('PHPUNIT_RUN')) { + // CLI should not hard-fail on broken memcache + $this->logger->info($missingCacheMessage, [ + 'class' => $distributedCacheClass, + 'use' => 'distributed', + 'app' => 'cli' + ]); + $distributedCacheClass = self::NULL_CACHE; + } else { + throw new \OC\HintException(strtr($missingCacheMessage, [ + '{class}' => $distributedCacheClass, '{use}' => 'distributed' + ]), $missingCacheHint); + } + } + if (!($lockingCacheClass && class_exists($distributedCacheClass) && $lockingCacheClass::isAvailable())) { + // don't fallback since the fallback might not be suitable for storing lock + $lockingCacheClass = self::NULL_CACHE; + } + + $this->localCacheClass = $localCacheClass; + $this->distributedCacheClass = $distributedCacheClass; + $this->lockingCacheClass = $lockingCacheClass; + } + + /** + * create a cache instance for storing locks + * + * @param string $prefix + * @return IMemcache + */ + public function createLocking(string $prefix = ''): IMemcache { + return new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix); + } + + /** + * create a distributed cache instance + * + * @param string $prefix + * @return ICache + */ + public function createDistributed(string $prefix = ''): ICache { + return new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix); + } + + /** + * create a local cache instance + * + * @param string $prefix + * @return ICache + */ + public function createLocal(string $prefix = ''): ICache { + return new $this->localCacheClass($this->globalPrefix . '/' . $prefix); + } + + /** + * @see \OC\Memcache\Factory::createDistributed() + * @param string $prefix + * @return ICache + * @deprecated 13.0.0 Use either createLocking, createDistributed or createLocal + */ + public function create(string $prefix = ''): ICache { + return $this->createDistributed($prefix); + } + + /** + * check memcache availability + * + * @return bool + */ + public function isAvailable(): bool { + return ($this->distributedCacheClass !== self::NULL_CACHE); + } + + /** + * @see \OC\Memcache\Factory::createLocal() + * @param string $prefix + * @return ICache + */ + public function createLowLatency(string $prefix = ''): ICache { + return $this->createLocal($prefix); + } + + /** + * Check if a local memory cache backend is available + * + * @return bool + */ + public function isLocalCacheAvailable(): bool { + return ($this->localCacheClass !== self::NULL_CACHE); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/Memcached.php b/docker/overlays/nextcloud/html/lib/private/Memcache/Memcached.php new file mode 100644 index 0000000..eff76a4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/Memcached.php @@ -0,0 +1,228 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +use OC\HintException; +use OCP\IMemcache; + +class Memcached extends Cache implements IMemcache { + use CASTrait; + + /** + * @var \Memcached $cache + */ + private static $cache = null; + + use CADTrait; + + public function __construct($prefix = '') { + parent::__construct($prefix); + if (is_null(self::$cache)) { + self::$cache = new \Memcached(); + + $defaultOptions = [ + \Memcached::OPT_CONNECT_TIMEOUT => 50, + \Memcached::OPT_RETRY_TIMEOUT => 50, + \Memcached::OPT_SEND_TIMEOUT => 50, + \Memcached::OPT_RECV_TIMEOUT => 50, + \Memcached::OPT_POLL_TIMEOUT => 50, + + // Enable compression + \Memcached::OPT_COMPRESSION => true, + + // Turn on consistent hashing + \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + + // Enable Binary Protocol + //\Memcached::OPT_BINARY_PROTOCOL => true, + ]; + // by default enable igbinary serializer if available + if (\Memcached::HAVE_IGBINARY) { + $defaultOptions[\Memcached::OPT_SERIALIZER] = + \Memcached::SERIALIZER_IGBINARY; + } + $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []); + if (is_array($options)) { + $options = $options + $defaultOptions; + self::$cache->setOptions($options); + } else { + throw new HintException("Expected 'memcached_options' config to be an array, got $options"); + } + + $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers'); + if (!$servers) { + $server = \OC::$server->getSystemConfig()->getValue('memcached_server'); + if ($server) { + $servers = [$server]; + } else { + $servers = [['localhost', 11211]]; + } + } + self::$cache->addServers($servers); + } + } + + /** + * entries in XCache gets namespaced to prevent collisions between owncloud instances and users + */ + protected function getNameSpace() { + return $this->prefix; + } + + public function get($key) { + $result = self::$cache->get($this->getNameSpace() . $key); + if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) { + return null; + } else { + return $result; + } + } + + public function set($key, $value, $ttl = 0) { + if ($ttl > 0) { + $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); + } else { + $result = self::$cache->set($this->getNameSpace() . $key, $value); + } + if ($result !== true) { + $this->verifyReturnCode(); + } + return $result; + } + + public function hasKey($key) { + self::$cache->get($this->getNameSpace() . $key); + return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; + } + + public function remove($key) { + $result= self::$cache->delete($this->getNameSpace() . $key); + if (self::$cache->getResultCode() !== \Memcached::RES_NOTFOUND) { + $this->verifyReturnCode(); + } + return $result; + } + + public function clear($prefix = '') { + $prefix = $this->getNameSpace() . $prefix; + $allKeys = self::$cache->getAllKeys(); + if ($allKeys === false) { + // newer Memcached doesn't like getAllKeys(), flush everything + self::$cache->flush(); + return true; + } + $keys = []; + $prefixLength = strlen($prefix); + foreach ($allKeys as $key) { + if (substr($key, 0, $prefixLength) === $prefix) { + $keys[] = $key; + } + } + if (method_exists(self::$cache, 'deleteMulti')) { + self::$cache->deleteMulti($keys); + } else { + foreach ($keys as $key) { + self::$cache->delete($key); + } + } + return true; + } + + /** + * Set a value in the cache if it's not already stored + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + * @throws \Exception + */ + public function add($key, $value, $ttl = 0) { + $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl); + if (self::$cache->getResultCode() !== \Memcached::RES_NOTSTORED) { + $this->verifyReturnCode(); + } + return $result; + } + + /** + * Increase a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function inc($key, $step = 1) { + $this->add($key, 0); + $result = self::$cache->increment($this->getPrefix() . $key, $step); + + if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { + return false; + } + + return $result; + } + + /** + * Decrease a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function dec($key, $step = 1) { + $result = self::$cache->decrement($this->getPrefix() . $key, $step); + + if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { + return false; + } + + return $result; + } + + public static function isAvailable() { + return extension_loaded('memcached'); + } + + /** + * @throws \Exception + */ + private function verifyReturnCode() { + $code = self::$cache->getResultCode(); + if ($code === \Memcached::RES_SUCCESS) { + return; + } + $message = self::$cache->getResultMessage(); + throw new \Exception("Error $code interacting with memcached : $message"); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/NullCache.php b/docker/overlays/nextcloud/html/lib/private/Memcache/NullCache.php new file mode 100644 index 0000000..bdb6406 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/NullCache.php @@ -0,0 +1,74 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +class NullCache extends Cache implements \OCP\IMemcache { + public function get($key) { + return null; + } + + public function set($key, $value, $ttl = 0) { + return true; + } + + public function hasKey($key) { + return false; + } + + public function remove($key) { + return true; + } + + public function add($key, $value, $ttl = 0) { + return true; + } + + public function inc($key, $step = 1) { + return true; + } + + public function dec($key, $step = 1) { + return true; + } + + public function cas($key, $old, $new) { + return true; + } + + public function cad($key, $old) { + return true; + } + + public function clear($prefix = '') { + return true; + } + + public static function isAvailable() { + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Memcache/Redis.php b/docker/overlays/nextcloud/html/lib/private/Memcache/Redis.php new file mode 100644 index 0000000..dfbdd02 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Memcache/Redis.php @@ -0,0 +1,181 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Memcache; + +use OCP\IMemcacheTTL; + +class Redis extends Cache implements IMemcacheTTL { + /** + * @var \Redis $cache + */ + private static $cache = null; + + public function __construct($prefix = '') { + parent::__construct($prefix); + if (is_null(self::$cache)) { + self::$cache = \OC::$server->getGetRedisFactory()->getInstance(); + } + } + + /** + * entries in redis get namespaced to prevent collisions between ownCloud instances and users + */ + protected function getNameSpace() { + return $this->prefix; + } + + public function get($key) { + $result = self::$cache->get($this->getNameSpace() . $key); + if ($result === false && !self::$cache->exists($this->getNameSpace() . $key)) { + return null; + } else { + return json_decode($result, true); + } + } + + public function set($key, $value, $ttl = 0) { + if ($ttl > 0) { + return self::$cache->setex($this->getNameSpace() . $key, $ttl, json_encode($value)); + } else { + return self::$cache->set($this->getNameSpace() . $key, json_encode($value)); + } + } + + public function hasKey($key) { + return self::$cache->exists($this->getNameSpace() . $key); + } + + public function remove($key) { + if (self::$cache->del($this->getNameSpace() . $key)) { + return true; + } else { + return false; + } + } + + public function clear($prefix = '') { + $prefix = $this->getNameSpace() . $prefix . '*'; + $keys = self::$cache->keys($prefix); + $deleted = self::$cache->del($keys); + + return count($keys) === $deleted; + } + + /** + * Set a value in the cache if it's not already stored + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + */ + public function add($key, $value, $ttl = 0) { + // don't encode ints for inc/dec + if (!is_int($value)) { + $value = json_encode($value); + } + return self::$cache->setnx($this->getPrefix() . $key, $value); + } + + /** + * Increase a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function inc($key, $step = 1) { + return self::$cache->incrBy($this->getNameSpace() . $key, $step); + } + + /** + * Decrease a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function dec($key, $step = 1) { + if (!$this->hasKey($key)) { + return false; + } + return self::$cache->decrBy($this->getNameSpace() . $key, $step); + } + + /** + * Compare and set + * + * @param string $key + * @param mixed $old + * @param mixed $new + * @return bool + */ + public function cas($key, $old, $new) { + if (!is_int($new)) { + $new = json_encode($new); + } + self::$cache->watch($this->getNameSpace() . $key); + if ($this->get($key) === $old) { + $result = self::$cache->multi() + ->set($this->getNameSpace() . $key, $new) + ->exec(); + return $result !== false; + } + self::$cache->unwatch(); + return false; + } + + /** + * Compare and delete + * + * @param string $key + * @param mixed $old + * @return bool + */ + public function cad($key, $old) { + self::$cache->watch($this->getNameSpace() . $key); + if ($this->get($key) === $old) { + $result = self::$cache->multi() + ->del($this->getNameSpace() . $key) + ->exec(); + return $result !== false; + } + self::$cache->unwatch(); + return false; + } + + public function setTTL($key, $ttl) { + self::$cache->expire($this->getNameSpace() . $key, $ttl); + } + + public static function isAvailable() { + return \OC::$server->getGetRedisFactory()->isAvailable(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/MemoryInfo.php b/docker/overlays/nextcloud/html/lib/private/MemoryInfo.php new file mode 100644 index 0000000..f227d38 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/MemoryInfo.php @@ -0,0 +1,86 @@ +) + * + * @author Christoph Wurst + * @author Michael Weimann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC; + +/** + * Helper class that covers memory info. + */ +class MemoryInfo { + public const RECOMMENDED_MEMORY_LIMIT = 512 * 1024 * 1024; + + /** + * Tests if the memory limit is greater or equal the recommended value. + * + * @return bool + */ + public function isMemoryLimitSufficient(): bool { + $memoryLimit = $this->getMemoryLimit(); + return $memoryLimit === -1 || $memoryLimit >= self::RECOMMENDED_MEMORY_LIMIT; + } + + /** + * Returns the php memory limit. + * + * @return int The memory limit in bytes. + */ + public function getMemoryLimit(): int { + $iniValue = trim(ini_get('memory_limit')); + if ($iniValue === '-1') { + return -1; + } elseif (is_numeric($iniValue) === true) { + return (int)$iniValue; + } else { + return $this->memoryLimitToBytes($iniValue); + } + } + + /** + * Converts the ini memory limit to bytes. + * + * @param string $memoryLimit The "memory_limit" ini value + * @return int + */ + private function memoryLimitToBytes(string $memoryLimit): int { + $last = strtolower(substr($memoryLimit, -1)); + $memoryLimit = (int)substr($memoryLimit, 0, -1); + + // intended fall trough + switch ($last) { + case 'g': + $memoryLimit *= 1024; + // no break + case 'm': + $memoryLimit *= 1024; + // no break + case 'k': + $memoryLimit *= 1024; + } + + return $memoryLimit; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Migration/BackgroundRepair.php b/docker/overlays/nextcloud/html/lib/private/Migration/BackgroundRepair.php new file mode 100644 index 0000000..fd616d5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Migration/BackgroundRepair.php @@ -0,0 +1,122 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Migration; + +use OC\BackgroundJob\JobList; +use OC\BackgroundJob\TimedJob; +use OC\NeedsUpdateException; +use OC\Repair; +use OC_App; +use OCP\BackgroundJob\IJobList; +use OCP\ILogger; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Class BackgroundRepair + * + * @package OC\Migration + */ +class BackgroundRepair extends TimedJob { + + /** @var IJobList */ + private $jobList; + + /** @var ILogger */ + private $logger; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) { + $this->dispatcher = $dispatcher; + } + + /** + * run the job, then remove it from the job list + * + * @param JobList $jobList + * @param ILogger|null $logger + */ + public function execute($jobList, ILogger $logger = null) { + // add an interval of 15 mins + $this->setInterval(15*60); + + $this->jobList = $jobList; + $this->logger = $logger; + parent::execute($jobList, $logger); + } + + /** + * @param array $argument + * @throws \Exception + * @throws \OC\NeedsUpdateException + */ + protected function run($argument) { + if (!isset($argument['app']) || !isset($argument['step'])) { + // remove the job - we can never execute it + $this->jobList->remove($this, $this->argument); + return; + } + $app = $argument['app']; + + try { + $this->loadApp($app); + } catch (NeedsUpdateException $ex) { + // as long as the app is not yet done with it's offline migration + // we better not start with the live migration + return; + } + + $step = $argument['step']; + $repair = new Repair([], $this->dispatcher); + try { + $repair->addStep($step); + } catch (\Exception $ex) { + $this->logger->logException($ex,[ + 'app' => 'migration' + ]); + + // remove the job - we can never execute it + $this->jobList->remove($this, $this->argument); + return; + } + + // execute the repair step + $repair->run(); + + // remove the job once executed successfully + $this->jobList->remove($this, $this->argument); + } + + /** + * @codeCoverageIgnore + * @param $app + * @throws NeedsUpdateException + */ + protected function loadApp($app) { + OC_App::loadApp($app); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Migration/ConsoleOutput.php b/docker/overlays/nextcloud/html/lib/private/Migration/ConsoleOutput.php new file mode 100644 index 0000000..2ad4284 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Migration/ConsoleOutput.php @@ -0,0 +1,92 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Migration; + +use OCP\Migration\IOutput; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class SimpleOutput + * + * Just a simple IOutput implementation with writes messages to the log file. + * Alternative implementations will write to the console or to the web ui (web update case) + * + * @package OC\Migration + */ +class ConsoleOutput implements IOutput { + + /** @var OutputInterface */ + private $output; + + /** @var ProgressBar */ + private $progressBar; + + public function __construct(OutputInterface $output) { + $this->output = $output; + } + + /** + * @param string $message + */ + public function info($message) { + $this->output->writeln("$message"); + } + + /** + * @param string $message + */ + public function warning($message) { + $this->output->writeln("$message"); + } + + /** + * @param int $max + */ + public function startProgress($max = 0) { + if (!is_null($this->progressBar)) { + $this->progressBar->finish(); + } + $this->progressBar = new ProgressBar($this->output); + $this->progressBar->start($max); + } + + /** + * @param int $step + * @param string $description + */ + public function advance($step = 1, $description = '') { + if (!is_null($this->progressBar)) { + $this->progressBar = new ProgressBar($this->output); + $this->progressBar->start(); + } + $this->progressBar->advance($step); + } + + public function finishProgress() { + if (is_null($this->progressBar)) { + return; + } + $this->progressBar->finish(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Migration/SimpleOutput.php b/docker/overlays/nextcloud/html/lib/private/Migration/SimpleOutput.php new file mode 100644 index 0000000..11f2ed2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Migration/SimpleOutput.php @@ -0,0 +1,83 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Migration; + +use OCP\ILogger; +use OCP\Migration\IOutput; + +/** + * Class SimpleOutput + * + * Just a simple IOutput implementation with writes messages to the log file. + * Alternative implementations will write to the console or to the web ui (web update case) + * + * @package OC\Migration + */ +class SimpleOutput implements IOutput { + + /** @var ILogger */ + private $logger; + private $appName; + + public function __construct(ILogger $logger, $appName) { + $this->logger = $logger; + $this->appName = $appName; + } + + /** + * @param string $message + * @since 9.1.0 + */ + public function info($message) { + $this->logger->info($message, ['app' => $this->appName]); + } + + /** + * @param string $message + * @since 9.1.0 + */ + public function warning($message) { + $this->logger->warning($message, ['app' => $this->appName]); + } + + /** + * @param int $max + * @since 9.1.0 + */ + public function startProgress($max = 0) { + } + + /** + * @param int $step + * @param string $description + * @since 9.1.0 + */ + public function advance($step = 1, $description = '') { + } + + /** + * @since 9.1.0 + */ + public function finishProgress() { + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/NaturalSort.php b/docker/overlays/nextcloud/html/lib/private/NaturalSort.php new file mode 100644 index 0000000..31829a8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/NaturalSort.php @@ -0,0 +1,142 @@ + + * @author AW-UC + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\ILogger; + +class NaturalSort { + private static $instance; + private $collator; + private $cache = []; + + /** + * Instantiate a new \OC\NaturalSort instance. + * @param object $injectedCollator + */ + public function __construct($injectedCollator = null) { + // inject an instance of \Collator('en_US') to force using the php5-intl Collator + // or inject an instance of \OC\NaturalSort_DefaultCollator to force using Owncloud's default collator + if (isset($injectedCollator)) { + $this->collator = $injectedCollator; + \OCP\Util::writeLog('core', 'forced use of '.get_class($injectedCollator), ILogger::DEBUG); + } + } + + /** + * Split the given string in chunks of numbers and strings + * @param string $t string + * @return array of strings and number chunks + */ + private function naturalSortChunkify($t) { + // Adapted and ported to PHP from + // http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + if (isset($this->cache[$t])) { + return $this->cache[$t]; + } + $tz = []; + $x = 0; + $y = -1; + $n = null; + + while (isset($t[$x])) { + $c = $t[$x]; + // only include the dot in strings + $m = ((!$n && $c === '.') || ($c >= '0' && $c <= '9')); + if ($m !== $n) { + // next chunk + $y++; + $tz[$y] = ''; + $n = $m; + } + $tz[$y] .= $c; + $x++; + } + $this->cache[$t] = $tz; + return $tz; + } + + /** + * Returns the string collator + * @return \Collator string collator + */ + private function getCollator() { + if (!isset($this->collator)) { + // looks like the default is en_US_POSIX which yields wrong sorting with + // German umlauts, so using en_US instead + if (class_exists('Collator')) { + $this->collator = new \Collator('en_US'); + } else { + $this->collator = new \OC\NaturalSort_DefaultCollator(); + } + } + return $this->collator; + } + + /** + * Compare two strings to provide a natural sort + * @param string $a first string to compare + * @param string $b second string to compare + * @return int -1 if $b comes before $a, 1 if $a comes before $b + * or 0 if the strings are identical + */ + public function compare($a, $b) { + // Needed because PHP doesn't sort correctly when numbers are enclosed in + // parenthesis, even with NUMERIC_COLLATION enabled. + // For example it gave ["test (2).txt", "test.txt"] + // instead of ["test.txt", "test (2).txt"] + $aa = self::naturalSortChunkify($a); + $bb = self::naturalSortChunkify($b); + + for ($x = 0; isset($aa[$x]) && isset($bb[$x]); $x++) { + $aChunk = $aa[$x]; + $bChunk = $bb[$x]; + if ($aChunk !== $bChunk) { + // test first character (character comparison, not number comparison) + if ($aChunk[0] >= '0' && $aChunk[0] <= '9' && $bChunk[0] >= '0' && $bChunk[0] <= '9') { + $aNum = (int)$aChunk; + $bNum = (int)$bChunk; + return $aNum - $bNum; + } + return self::getCollator()->compare($aChunk, $bChunk); + } + } + return count($aa) - count($bb); + } + + /** + * Returns a singleton + * @return \OC\NaturalSort instance + */ + public static function getInstance() { + if (!isset(self::$instance)) { + self::$instance = new \OC\NaturalSort(); + } + return self::$instance; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/NaturalSort_DefaultCollator.php b/docker/overlays/nextcloud/html/lib/private/NaturalSort_DefaultCollator.php new file mode 100644 index 0000000..f19b821 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/NaturalSort_DefaultCollator.php @@ -0,0 +1,38 @@ + + * @author Joas Schilling + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class NaturalSort_DefaultCollator { + public function compare($a, $b) { + $result = strcasecmp($a, $b); + if ($result === 0) { + if ($a === $b) { + return 0; + } + return ($a > $b) ? -1 : 1; + } + return ($result < 0) ? -1 : 1; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/NavigationManager.php b/docker/overlays/nextcloud/html/lib/private/NavigationManager.php new file mode 100644 index 0000000..e250523 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/NavigationManager.php @@ -0,0 +1,323 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\App\AppManager; +use OC\Group\Manager; +use OCP\App\IAppManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\INavigationManager; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\L10N\IFactory; + +/** + * Manages the ownCloud navigation + */ + +class NavigationManager implements INavigationManager { + protected $entries = []; + protected $closureEntries = []; + protected $activeEntry; + /** @var bool */ + protected $init = false; + /** @var IAppManager|AppManager */ + protected $appManager; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IFactory */ + private $l10nFac; + /** @var IUserSession */ + private $userSession; + /** @var IGroupManager|Manager */ + private $groupManager; + /** @var IConfig */ + private $config; + + public function __construct(IAppManager $appManager, + IURLGenerator $urlGenerator, + IFactory $l10nFac, + IUserSession $userSession, + IGroupManager $groupManager, + IConfig $config) { + $this->appManager = $appManager; + $this->urlGenerator = $urlGenerator; + $this->l10nFac = $l10nFac; + $this->userSession = $userSession; + $this->groupManager = $groupManager; + $this->config = $config; + } + + /** + * @inheritDoc + */ + public function add($entry) { + if ($entry instanceof \Closure) { + $this->closureEntries[] = $entry; + return; + } + + $entry['active'] = false; + if (!isset($entry['icon'])) { + $entry['icon'] = ''; + } + if (!isset($entry['classes'])) { + $entry['classes'] = ''; + } + if (!isset($entry['type'])) { + $entry['type'] = 'link'; + } + $this->entries[$entry['id']] = $entry; + } + + /** + * @inheritDoc + */ + public function getAll(string $type = 'link'): array { + $this->init(); + foreach ($this->closureEntries as $c) { + $this->add($c()); + } + $this->closureEntries = []; + + $result = $this->entries; + if ($type !== 'all') { + $result = array_filter($this->entries, function ($entry) use ($type) { + return $entry['type'] === $type; + }); + } + + return $this->proceedNavigation($result); + } + + /** + * Sort navigation entries by order, name and set active flag + * + * @param array $list + * @return array + */ + private function proceedNavigation(array $list): array { + uasort($list, function ($a, $b) { + if (isset($a['order']) && isset($b['order'])) { + return ($a['order'] < $b['order']) ? -1 : 1; + } elseif (isset($a['order']) || isset($b['order'])) { + return isset($a['order']) ? -1 : 1; + } else { + return ($a['name'] < $b['name']) ? -1 : 1; + } + }); + + $activeApp = $this->getActiveEntry(); + if ($activeApp !== null) { + foreach ($list as $index => &$navEntry) { + if ($navEntry['id'] == $activeApp) { + $navEntry['active'] = true; + } else { + $navEntry['active'] = false; + } + } + unset($navEntry); + } + + return $list; + } + + + /** + * removes all the entries + */ + public function clear($loadDefaultLinks = true) { + $this->entries = []; + $this->closureEntries = []; + $this->init = !$loadDefaultLinks; + } + + /** + * @inheritDoc + */ + public function setActiveEntry($id) { + $this->activeEntry = $id; + } + + /** + * @inheritDoc + */ + public function getActiveEntry() { + return $this->activeEntry; + } + + private function init() { + if ($this->init) { + return; + } + $this->init = true; + + $l = $this->l10nFac->get('lib'); + if ($this->config->getSystemValue('knowledgebaseenabled', true)) { + $this->add([ + 'type' => 'settings', + 'id' => 'help', + 'order' => 6, + 'href' => $this->urlGenerator->linkToRoute('settings.Help.help'), + 'name' => $l->t('Help'), + 'icon' => $this->urlGenerator->imagePath('settings', 'help.svg'), + ]); + } + + if ($this->userSession->isLoggedIn()) { + if ($this->isAdmin()) { + // App management + $this->add([ + 'type' => 'settings', + 'id' => 'core_apps', + 'order' => 4, + 'href' => $this->urlGenerator->linkToRoute('settings.AppSettings.viewApps'), + 'icon' => $this->urlGenerator->imagePath('settings', 'apps.svg'), + 'name' => $l->t('Apps'), + ]); + } + + // Personal and (if applicable) admin settings + $this->add([ + 'type' => 'settings', + 'id' => 'settings', + 'order' => 2, + 'href' => $this->urlGenerator->linkToRoute('settings.PersonalSettings.index'), + 'name' => $l->t('Settings'), + 'icon' => $this->urlGenerator->imagePath('settings', 'admin.svg'), + ]); + + $logoutUrl = \OC_User::getLogoutUrl($this->urlGenerator); + if ($logoutUrl !== '') { + // Logout + $this->add([ + 'type' => 'settings', + 'id' => 'logout', + 'order' => 99999, + 'href' => $logoutUrl, + 'name' => $l->t('Log out'), + 'icon' => $this->urlGenerator->imagePath('core', 'actions/logout.svg'), + ]); + } + + if ($this->isSubadmin()) { + // User management + $this->add([ + 'type' => 'settings', + 'id' => 'core_users', + 'order' => 5, + 'href' => $this->urlGenerator->linkToRoute('settings.Users.usersList'), + 'name' => $l->t('Users'), + 'icon' => $this->urlGenerator->imagePath('settings', 'users.svg'), + ]); + } + } + + if ($this->appManager === 'null') { + return; + } + + if ($this->userSession->isLoggedIn()) { + $apps = $this->appManager->getEnabledAppsForUser($this->userSession->getUser()); + } else { + $apps = $this->appManager->getInstalledApps(); + } + + foreach ($apps as $app) { + if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) { + continue; + } + + // load plugins and collections from info.xml + $info = $this->appManager->getAppInfo($app); + if (!isset($info['navigations']['navigation'])) { + continue; + } + foreach ($info['navigations']['navigation'] as $key => $nav) { + if (!isset($nav['name'])) { + continue; + } + if (!isset($nav['route'])) { + continue; + } + $role = isset($nav['@attributes']['role']) ? $nav['@attributes']['role'] : 'all'; + if ($role === 'admin' && !$this->isAdmin()) { + continue; + } + $l = $this->l10nFac->get($app); + $id = $nav['id'] ?? $app . ($key === 0 ? '' : $key); + $order = isset($nav['order']) ? $nav['order'] : 100; + $type = isset($nav['type']) ? $nav['type'] : 'link'; + $route = $nav['route'] !== '' ? $this->urlGenerator->linkToRoute($nav['route']) : ''; + $icon = isset($nav['icon']) ? $nav['icon'] : 'app.svg'; + foreach ([$icon, "$app.svg"] as $i) { + try { + $icon = $this->urlGenerator->imagePath($app, $i); + break; + } catch (\RuntimeException $ex) { + // no icon? - ignore it then + } + } + if ($icon === null) { + $icon = $this->urlGenerator->imagePath('core', 'default-app-icon'); + } + + $this->add([ + 'id' => $id, + 'order' => $order, + 'href' => $route, + 'icon' => $icon, + 'type' => $type, + 'name' => $l->t($nav['name']), + ]); + } + } + } + + private function isAdmin() { + $user = $this->userSession->getUser(); + if ($user !== null) { + return $this->groupManager->isAdmin($user->getUID()); + } + return false; + } + + private function isSubadmin() { + $user = $this->userSession->getUser(); + if ($user !== null) { + return $this->groupManager->getSubAdmin()->isSubAdmin($user); + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/NeedsUpdateException.php b/docker/overlays/nextcloud/html/lib/private/NeedsUpdateException.php new file mode 100644 index 0000000..0b9d323 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/NeedsUpdateException.php @@ -0,0 +1,27 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class NeedsUpdateException extends ServiceUnavailableException { +} diff --git a/docker/overlays/nextcloud/html/lib/private/NotSquareException.php b/docker/overlays/nextcloud/html/lib/private/NotSquareException.php new file mode 100644 index 0000000..a67fac7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/NotSquareException.php @@ -0,0 +1,27 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class NotSquareException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Notification/Action.php b/docker/overlays/nextcloud/html/lib/private/Notification/Action.php new file mode 100644 index 0000000..a04f622 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Notification/Action.php @@ -0,0 +1,174 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Notification; + +use OCP\Notification\IAction; + +class Action implements IAction { + + /** @var string */ + protected $label; + + /** @var string */ + protected $labelParsed; + + /** @var string */ + protected $link; + + /** @var string */ + protected $requestType; + + /** @var string */ + protected $icon; + + /** @var bool */ + protected $primary; + + public function __construct() { + $this->label = ''; + $this->labelParsed = ''; + $this->link = ''; + $this->requestType = ''; + $this->primary = false; + } + + /** + * @param string $label + * @return $this + * @throws \InvalidArgumentException if the label is invalid + * @since 8.2.0 + */ + public function setLabel(string $label): IAction { + if ($label === '' || isset($label[32])) { + throw new \InvalidArgumentException('The given label is invalid'); + } + $this->label = $label; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getLabel(): string { + return $this->label; + } + + /** + * @param string $label + * @return $this + * @throws \InvalidArgumentException if the label is invalid + * @since 8.2.0 + */ + public function setParsedLabel(string $label): IAction { + if ($label === '') { + throw new \InvalidArgumentException('The given parsed label is invalid'); + } + $this->labelParsed = $label; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getParsedLabel(): string { + return $this->labelParsed; + } + + /** + * @param $primary bool + * @return $this + * @since 9.0.0 + */ + public function setPrimary(bool $primary): IAction { + $this->primary = $primary; + return $this; + } + + /** + * @return bool + * @since 9.0.0 + */ + public function isPrimary(): bool { + return $this->primary; + } + + /** + * @param string $link + * @param string $requestType + * @return $this + * @throws \InvalidArgumentException if the link is invalid + * @since 8.2.0 + */ + public function setLink(string $link, string $requestType): IAction { + if ($link === '' || isset($link[256])) { + throw new \InvalidArgumentException('The given link is invalid'); + } + if (!in_array($requestType, [ + self::TYPE_GET, + self::TYPE_POST, + self::TYPE_PUT, + self::TYPE_DELETE, + self::TYPE_WEB, + ], true)) { + throw new \InvalidArgumentException('The given request type is invalid'); + } + $this->link = $link; + $this->requestType = $requestType; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getLink(): string { + return $this->link; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getRequestType(): string { + return $this->requestType; + } + + /** + * @return bool + */ + public function isValid(): bool { + return $this->label !== '' && $this->link !== ''; + } + + /** + * @return bool + */ + public function isValidParsed(): bool { + return $this->labelParsed !== '' && $this->link !== ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Notification/Manager.php b/docker/overlays/nextcloud/html/lib/private/Notification/Manager.php new file mode 100644 index 0000000..f485b37 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Notification/Manager.php @@ -0,0 +1,358 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Notification; + +use OCP\AppFramework\QueryException; +use OCP\ILogger; +use OCP\Notification\AlreadyProcessedException; +use OCP\Notification\IApp; +use OCP\Notification\IDeferrableApp; +use OCP\Notification\IDismissableNotifier; +use OCP\Notification\IManager; +use OCP\Notification\INotification; +use OCP\Notification\INotifier; +use OCP\RichObjectStrings\IValidator; + +class Manager implements IManager { + /** @var IValidator */ + protected $validator; + /** @var ILogger */ + protected $logger; + + /** @var IApp[] */ + protected $apps; + /** @var string[] */ + protected $appClasses; + + /** @var INotifier[] */ + protected $notifiers; + /** @var string[] */ + protected $notifierClasses; + + /** @var bool */ + protected $preparingPushNotification; + /** @var bool */ + protected $deferPushing; + + public function __construct(IValidator $validator, + ILogger $logger) { + $this->validator = $validator; + $this->logger = $logger; + $this->apps = []; + $this->notifiers = []; + $this->appClasses = []; + $this->notifierClasses = []; + $this->preparingPushNotification = false; + $this->deferPushing = false; + } + /** + * @param string $appClass The service must implement IApp, otherwise a + * \InvalidArgumentException is thrown later + * @since 17.0.0 + */ + public function registerApp(string $appClass): void { + $this->appClasses[] = $appClass; + } + + /** + * @param \Closure $service The service must implement INotifier, otherwise a + * \InvalidArgumentException is thrown later + * @param \Closure $info An array with the keys 'id' and 'name' containing + * the app id and the app name + * @deprecated 17.0.0 use registerNotifierService instead. + * @since 8.2.0 - Parameter $info was added in 9.0.0 + */ + public function registerNotifier(\Closure $service, \Closure $info) { + $infoData = $info(); + $this->logger->logException(new \InvalidArgumentException( + 'Notifier ' . $infoData['name'] . ' (id: ' . $infoData['id'] . ') is not considered because it is using the old way to register.' + )); + } + + /** + * @param string $notifierService The service must implement INotifier, otherwise a + * \InvalidArgumentException is thrown later + * @since 17.0.0 + */ + public function registerNotifierService(string $notifierService): void { + $this->notifierClasses[] = $notifierService; + } + + /** + * @return IApp[] + */ + protected function getApps(): array { + if (empty($this->appClasses)) { + return $this->apps; + } + + foreach ($this->appClasses as $appClass) { + try { + $app = \OC::$server->query($appClass); + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'message' => 'Failed to load notification app class: ' . $appClass, + 'app' => 'notifications', + ]); + continue; + } + + if (!($app instanceof IApp)) { + $this->logger->error('Notification app class ' . $appClass . ' is not implementing ' . IApp::class, [ + 'app' => 'notifications', + ]); + continue; + } + + $this->apps[] = $app; + } + + $this->appClasses = []; + + return $this->apps; + } + + /** + * @return INotifier[] + */ + public function getNotifiers(): array { + if (empty($this->notifierClasses)) { + return $this->notifiers; + } + + foreach ($this->notifierClasses as $notifierClass) { + try { + $notifier = \OC::$server->query($notifierClass); + } catch (QueryException $e) { + $this->logger->logException($e, [ + 'message' => 'Failed to load notification notifier class: ' . $notifierClass, + 'app' => 'notifications', + ]); + continue; + } + + if (!($notifier instanceof INotifier)) { + $this->logger->error('Notification notifier class ' . $notifierClass . ' is not implementing ' . INotifier::class, [ + 'app' => 'notifications', + ]); + continue; + } + + $this->notifiers[] = $notifier; + } + + $this->notifierClasses = []; + + return $this->notifiers; + } + + /** + * @return INotification + * @since 8.2.0 + */ + public function createNotification(): INotification { + return new Notification($this->validator); + } + + /** + * @return bool + * @since 8.2.0 + */ + public function hasNotifiers(): bool { + return !empty($this->notifiers) || !empty($this->notifierClasses); + } + + /** + * @param bool $preparingPushNotification + * @since 14.0.0 + */ + public function setPreparingPushNotification(bool $preparingPushNotification): void { + $this->preparingPushNotification = $preparingPushNotification; + } + + /** + * @return bool + * @since 14.0.0 + */ + public function isPreparingPushNotification(): bool { + return $this->preparingPushNotification; + } + + /** + * The calling app should only "flush" when it got returned true on the defer call + * @return bool + * @since 20.0.0 + */ + public function defer(): bool { + $alreadyDeferring = $this->deferPushing; + $this->deferPushing = true; + + $apps = $this->getApps(); + + foreach ($apps as $app) { + if ($app instanceof IDeferrableApp) { + $app->defer(); + } + } + + return !$alreadyDeferring; + } + + /** + * @since 20.0.0 + */ + public function flush(): void { + $apps = $this->getApps(); + + foreach ($apps as $app) { + if (!$app instanceof IDeferrableApp) { + continue; + } + + try { + $app->flush(); + } catch (\InvalidArgumentException $e) { + } + } + + $this->deferPushing = false; + } + + /** + * @param INotification $notification + * @throws \InvalidArgumentException When the notification is not valid + * @since 8.2.0 + */ + public function notify(INotification $notification): void { + if (!$notification->isValid()) { + throw new \InvalidArgumentException('The given notification is invalid'); + } + + $apps = $this->getApps(); + + foreach ($apps as $app) { + try { + $app->notify($notification); + } catch (\InvalidArgumentException $e) { + } + } + } + + /** + * Identifier of the notifier, only use [a-z0-9_] + * + * @return string + * @since 17.0.0 + */ + public function getID(): string { + return 'core'; + } + + /** + * Human readable name describing the notifier + * + * @return string + * @since 17.0.0 + */ + public function getName(): string { + return 'core'; + } + + /** + * @param INotification $notification + * @param string $languageCode The code of the language that should be used to prepare the notification + * @return INotification + * @throws \InvalidArgumentException When the notification was not prepared by a notifier + * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted + * @since 8.2.0 + */ + public function prepare(INotification $notification, string $languageCode): INotification { + $notifiers = $this->getNotifiers(); + + foreach ($notifiers as $notifier) { + try { + $notification = $notifier->prepare($notification, $languageCode); + } catch (\InvalidArgumentException $e) { + continue; + } catch (AlreadyProcessedException $e) { + $this->markProcessed($notification); + throw new \InvalidArgumentException('The given notification has been processed'); + } + + if (!($notification instanceof INotification) || !$notification->isValidParsed()) { + throw new \InvalidArgumentException('The given notification has not been handled'); + } + } + + if (!($notification instanceof INotification) || !$notification->isValidParsed()) { + throw new \InvalidArgumentException('The given notification has not been handled'); + } + + return $notification; + } + + /** + * @param INotification $notification + */ + public function markProcessed(INotification $notification): void { + $apps = $this->getApps(); + + foreach ($apps as $app) { + $app->markProcessed($notification); + } + } + + /** + * @param INotification $notification + * @return int + */ + public function getCount(INotification $notification): int { + $apps = $this->getApps(); + + $count = 0; + foreach ($apps as $app) { + $count += $app->getCount($notification); + } + + return $count; + } + + public function dismissNotification(INotification $notification): void { + $notifiers = $this->getNotifiers(); + + foreach ($notifiers as $notifier) { + if ($notifier instanceof IDismissableNotifier) { + try { + $notifier->dismissNotification($notification); + } catch (\InvalidArgumentException $e) { + continue; + } + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Notification/Notification.php b/docker/overlays/nextcloud/html/lib/private/Notification/Notification.php new file mode 100644 index 0000000..8ac4e19 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Notification/Notification.php @@ -0,0 +1,580 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Notification; + +use OCP\Notification\IAction; +use OCP\Notification\INotification; +use OCP\RichObjectStrings\InvalidObjectExeption; +use OCP\RichObjectStrings\IValidator; + +class Notification implements INotification { + + /** @var IValidator */ + protected $richValidator; + + /** @var string */ + protected $app; + + /** @var string */ + protected $user; + + /** @var \DateTime */ + protected $dateTime; + + /** @var string */ + protected $objectType; + + /** @var string */ + protected $objectId; + + /** @var string */ + protected $subject; + + /** @var array */ + protected $subjectParameters; + + /** @var string */ + protected $subjectParsed; + + /** @var string */ + protected $subjectRich; + + /** @var array */ + protected $subjectRichParameters; + + /** @var string */ + protected $message; + + /** @var array */ + protected $messageParameters; + + /** @var string */ + protected $messageParsed; + + /** @var string */ + protected $messageRich; + + /** @var array */ + protected $messageRichParameters; + + /** @var string */ + protected $link; + + /** @var string */ + protected $icon; + + /** @var array */ + protected $actions; + + /** @var array */ + protected $actionsParsed; + + /** @var bool */ + protected $hasPrimaryAction; + + /** @var bool */ + protected $hasPrimaryParsedAction; + + public function __construct(IValidator $richValidator) { + $this->richValidator = $richValidator; + $this->app = ''; + $this->user = ''; + $this->dateTime = new \DateTime(); + $this->dateTime->setTimestamp(0); + $this->objectType = ''; + $this->objectId = ''; + $this->subject = ''; + $this->subjectParameters = []; + $this->subjectParsed = ''; + $this->subjectRich = ''; + $this->subjectRichParameters = []; + $this->message = ''; + $this->messageParameters = []; + $this->messageParsed = ''; + $this->messageRich = ''; + $this->messageRichParameters = []; + $this->link = ''; + $this->icon = ''; + $this->actions = []; + $this->actionsParsed = []; + } + + /** + * @param string $app + * @return $this + * @throws \InvalidArgumentException if the app id is invalid + * @since 8.2.0 + */ + public function setApp(string $app): INotification { + if ($app === '' || isset($app[32])) { + throw new \InvalidArgumentException('The given app name is invalid'); + } + $this->app = $app; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getApp(): string { + return $this->app; + } + + /** + * @param string $user + * @return $this + * @throws \InvalidArgumentException if the user id is invalid + * @since 8.2.0 + */ + public function setUser(string $user): INotification { + if ($user === '' || isset($user[64])) { + throw new \InvalidArgumentException('The given user id is invalid'); + } + $this->user = $user; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getUser(): string { + return $this->user; + } + + /** + * @param \DateTime $dateTime + * @return $this + * @throws \InvalidArgumentException if the $dateTime is invalid + * @since 9.0.0 + */ + public function setDateTime(\DateTime $dateTime): INotification { + if ($dateTime->getTimestamp() === 0) { + throw new \InvalidArgumentException('The given date time is invalid'); + } + $this->dateTime = $dateTime; + return $this; + } + + /** + * @return \DateTime + * @since 9.0.0 + */ + public function getDateTime(): \DateTime { + return $this->dateTime; + } + + /** + * @param string $type + * @param string $id + * @return $this + * @throws \InvalidArgumentException if the object type or id is invalid + * @since 8.2.0 - 9.0.0: Type of $id changed to string + */ + public function setObject(string $type, string $id): INotification { + if ($type === '' || isset($type[64])) { + throw new \InvalidArgumentException('The given object type is invalid'); + } + $this->objectType = $type; + + if ($id === '' || isset($id[64])) { + throw new \InvalidArgumentException('The given object id is invalid'); + } + $this->objectId = (string) $id; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getObjectType(): string { + return $this->objectType; + } + + /** + * @return string + * @since 8.2.0 - 9.0.0: Return type changed to string + */ + public function getObjectId(): string { + return $this->objectId; + } + + /** + * @param string $subject + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 8.2.0 + */ + public function setSubject(string $subject, array $parameters = []): INotification { + if ($subject === '' || isset($subject[64])) { + throw new \InvalidArgumentException('The given subject is invalid'); + } + + $this->subject = $subject; + $this->subjectParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getSubject(): string { + return $this->subject; + } + + /** + * @return array + * @since 8.2.0 + */ + public function getSubjectParameters(): array { + return $this->subjectParameters; + } + + /** + * @param string $subject + * @return $this + * @throws \InvalidArgumentException if the subject is invalid + * @since 8.2.0 + */ + public function setParsedSubject(string $subject): INotification { + if ($subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + $this->subjectParsed = $subject; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getParsedSubject(): string { + return $this->subjectParsed; + } + + /** + * @param string $subject + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichSubject(string $subject, array $parameters = []): INotification { + if ($subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + + $this->subjectRich = $subject; + $this->subjectRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichSubject(): string { + return $this->subjectRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichSubjectParameters(): array { + return $this->subjectRichParameters; + } + + /** + * @param string $message + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the message or parameters are invalid + * @since 8.2.0 + */ + public function setMessage(string $message, array $parameters = []): INotification { + if ($message === '' || isset($message[64])) { + throw new \InvalidArgumentException('The given message is invalid'); + } + + $this->message = $message; + $this->messageParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getMessage(): string { + return $this->message; + } + + /** + * @return array + * @since 8.2.0 + */ + public function getMessageParameters(): array { + return $this->messageParameters; + } + + /** + * @param string $message + * @return $this + * @throws \InvalidArgumentException if the message is invalid + * @since 8.2.0 + */ + public function setParsedMessage(string $message): INotification { + if ($message === '') { + throw new \InvalidArgumentException('The given parsed message is invalid'); + } + $this->messageParsed = $message; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getParsedMessage(): string { + return $this->messageParsed; + } + + /** + * @param string $message + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the message or parameters are invalid + * @since 11.0.0 + */ + public function setRichMessage(string $message, array $parameters = []): INotification { + if ($message === '') { + throw new \InvalidArgumentException('The given parsed message is invalid'); + } + + $this->messageRich = $message; + $this->messageRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichMessage(): string { + return $this->messageRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichMessageParameters(): array { + return $this->messageRichParameters; + } + + /** + * @param string $link + * @return $this + * @throws \InvalidArgumentException if the link is invalid + * @since 8.2.0 + */ + public function setLink(string $link): INotification { + if ($link === '' || isset($link[4000])) { + throw new \InvalidArgumentException('The given link is invalid'); + } + $this->link = $link; + return $this; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getLink(): string { + return $this->link; + } + + /** + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 11.0.0 + */ + public function setIcon(string $icon): INotification { + if ($icon === '' || isset($icon[4000])) { + throw new \InvalidArgumentException('The given icon is invalid'); + } + $this->icon = $icon; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getIcon(): string { + return $this->icon; + } + + /** + * @return IAction + * @since 8.2.0 + */ + public function createAction(): IAction { + return new Action(); + } + + /** + * @param IAction $action + * @return $this + * @throws \InvalidArgumentException if the action is invalid + * @since 8.2.0 + */ + public function addAction(IAction $action): INotification { + if (!$action->isValid()) { + throw new \InvalidArgumentException('The given action is invalid'); + } + + if ($action->isPrimary()) { + if ($this->hasPrimaryAction) { + throw new \InvalidArgumentException('The notification already has a primary action'); + } + + $this->hasPrimaryAction = true; + } + + $this->actions[] = $action; + return $this; + } + + /** + * @return IAction[] + * @since 8.2.0 + */ + public function getActions(): array { + return $this->actions; + } + + /** + * @param IAction $action + * @return $this + * @throws \InvalidArgumentException if the action is invalid + * @since 8.2.0 + */ + public function addParsedAction(IAction $action): INotification { + if (!$action->isValidParsed()) { + throw new \InvalidArgumentException('The given parsed action is invalid'); + } + + if ($action->isPrimary()) { + if ($this->hasPrimaryParsedAction) { + throw new \InvalidArgumentException('The notification already has a primary action'); + } + + $this->hasPrimaryParsedAction = true; + + // Make sure the primary action is always the first one + array_unshift($this->actionsParsed, $action); + } else { + $this->actionsParsed[] = $action; + } + + return $this; + } + + /** + * @return IAction[] + * @since 8.2.0 + */ + public function getParsedActions(): array { + return $this->actionsParsed; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValid(): bool { + return + $this->isValidCommon() + && + $this->getSubject() !== '' + ; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValidParsed(): bool { + if ($this->getRichSubject() !== '' || !empty($this->getRichSubjectParameters())) { + try { + $this->richValidator->validate($this->getRichSubject(), $this->getRichSubjectParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + if ($this->getRichMessage() !== '' || !empty($this->getRichMessageParameters())) { + try { + $this->richValidator->validate($this->getRichMessage(), $this->getRichMessageParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + return + $this->isValidCommon() + && + $this->getParsedSubject() !== '' + ; + } + + protected function isValidCommon(): bool { + return + $this->getApp() !== '' + && + $this->getUser() !== '' + && + $this->getDateTime()->getTimestamp() !== 0 + && + $this->getObjectType() !== '' + && + $this->getObjectId() !== '' + ; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/OCS/CoreCapabilities.php b/docker/overlays/nextcloud/html/lib/private/OCS/CoreCapabilities.php new file mode 100644 index 0000000..81c7bf9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/OCS/CoreCapabilities.php @@ -0,0 +1,59 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\OCS; + +use OCP\Capabilities\ICapability; +use OCP\IConfig; + +/** + * Class Capabilities + * + * @package OC\OCS + */ +class CoreCapabilities implements ICapability { + + /** @var IConfig */ + private $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Return this classes capabilities + * + * @return array + */ + public function getCapabilities() { + return [ + 'core' => [ + 'pollinterval' => $this->config->getSystemValue('pollinterval', 60), + 'webdav-root' => $this->config->getSystemValue('webdav-root', 'remote.php/webdav'), + ] + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/OCS/DiscoveryService.php b/docker/overlays/nextcloud/html/lib/private/OCS/DiscoveryService.php new file mode 100644 index 0000000..1c69d1e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/OCS/DiscoveryService.php @@ -0,0 +1,135 @@ + + * + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\OCS; + +use OCP\AppFramework\Http; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\OCS\IDiscoveryService; + +class DiscoveryService implements IDiscoveryService { + + /** @var ICache */ + private $cache; + + /** @var IClient */ + private $client; + + /** + * @param ICacheFactory $cacheFactory + * @param IClientService $clientService + */ + public function __construct(ICacheFactory $cacheFactory, + IClientService $clientService + ) { + $this->cache = $cacheFactory->createDistributed('ocs-discovery'); + $this->client = $clientService->newClient(); + } + + + /** + * Discover OCS end-points + * + * If no valid discovery data is found the defaults are returned + * + * @param string $remote + * @param string $service the service you want to discover + * @param bool $skipCache We won't check if the data is in the cache. This is usefull if a background job is updating the status + * @return array + */ + public function discover(string $remote, string $service, bool $skipCache = false): array { + // Check the cache first + if ($skipCache === false) { + $cacheData = $this->cache->get($remote . '#' . $service); + if ($cacheData) { + $data = json_decode($cacheData, true); + if (\is_array($data)) { + return $data; + } + } + } + + $discoveredServices = []; + + // query the remote server for available services + try { + $response = $this->client->get($remote . '/ocs-provider/', [ + 'timeout' => 10, + 'connect_timeout' => 10, + ]); + if ($response->getStatusCode() === Http::STATUS_OK) { + $decodedServices = json_decode($response->getBody(), true); + if (\is_array($decodedServices)) { + $discoveredServices = $this->getEndpoints($decodedServices, $service); + } + } + } catch (\Exception $e) { + // if we couldn't discover the service or any end-points we return a empty array + } + + // Write into cache + $this->cache->set($remote . '#' . $service, json_encode($discoveredServices), 60*60*24); + return $discoveredServices; + } + + /** + * get requested end-points from the requested service + * + * @param array $decodedServices + * @param string $service + * @return array + */ + protected function getEndpoints(array $decodedServices, string $service): array { + $discoveredServices = []; + + if (isset($decodedServices['services'][$service]['endpoints'])) { + foreach ($decodedServices['services'][$service]['endpoints'] as $endpoint => $url) { + if ($this->isSafeUrl($url)) { + $discoveredServices[$endpoint] = $url; + } + } + } + + return $discoveredServices; + } + + /** + * Returns whether the specified URL includes only safe characters, if not + * returns false + * + * @param string $url + * @return bool + */ + protected function isSafeUrl(string $url): bool { + return (bool)preg_match('/^[\/\.\-A-Za-z0-9]+$/', $url); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/OCS/Exception.php b/docker/overlays/nextcloud/html/lib/private/OCS/Exception.php new file mode 100644 index 0000000..63a7bdb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/OCS/Exception.php @@ -0,0 +1,39 @@ + + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\OCS; + +class Exception extends \Exception { + + /** @var Result */ + private $result; + + public function __construct(Result $result) { + parent::__construct(); + $this->result = $result; + } + + public function getResult() { + return $this->result; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/OCS/Provider.php b/docker/overlays/nextcloud/html/lib/private/OCS/Provider.php new file mode 100644 index 0000000..68100c6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/OCS/Provider.php @@ -0,0 +1,117 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\OCS; + +class Provider extends \OCP\AppFramework\Controller { + /** @var \OCP\App\IAppManager */ + private $appManager; + + /** + * @param string $appName + * @param \OCP\IRequest $request + * @param \OCP\App\IAppManager $appManager + */ + public function __construct($appName, + \OCP\IRequest $request, + \OCP\App\IAppManager $appManager) { + parent::__construct($appName, $request); + $this->appManager = $appManager; + } + + /** + * @return \OCP\AppFramework\Http\JSONResponse + */ + public function buildProviderList() { + $services = [ + 'PRIVATE_DATA' => [ + 'version' => 1, + 'endpoints' => [ + 'store' => '/ocs/v2.php/privatedata/setattribute', + 'read' => '/ocs/v2.php/privatedata/getattribute', + 'delete' => '/ocs/v2.php/privatedata/deleteattribute', + ], + ], + ]; + + if ($this->appManager->isEnabledForUser('files_sharing')) { + $services['SHARING'] = [ + 'version' => 1, + 'endpoints' => [ + 'share' => '/ocs/v2.php/apps/files_sharing/api/v1/shares', + ], + ]; + $services['FEDERATED_SHARING'] = [ + 'version' => 1, + 'endpoints' => [ + 'share' => '/ocs/v2.php/cloud/shares', + 'webdav' => '/public.php/webdav/', + ], + ]; + } + + if ($this->appManager->isEnabledForUser('federation')) { + if (isset($services['FEDERATED_SHARING'])) { + $services['FEDERATED_SHARING']['endpoints']['shared-secret'] = '/ocs/v2.php/cloud/shared-secret'; + $services['FEDERATED_SHARING']['endpoints']['system-address-book'] = '/remote.php/dav/addressbooks/system/system/system'; + $services['FEDERATED_SHARING']['endpoints']['carddav-user'] = 'system'; + } else { + $services['FEDERATED_SHARING'] = [ + 'version' => 1, + 'endpoints' => [ + 'shared-secret' => '/ocs/v2.php/cloud/shared-secret', + 'system-address-book' => '/remote.php/dav/addressbooks/system/system/system', + 'carddav-user' => 'system' + ], + ]; + } + } + + if ($this->appManager->isEnabledForUser('activity')) { + $services['ACTIVITY'] = [ + 'version' => 1, + 'endpoints' => [ + 'list' => '/ocs/v2.php/cloud/activity', + ], + ]; + } + + if ($this->appManager->isEnabledForUser('provisioning_api')) { + $services['PROVISIONING'] = [ + 'version' => 1, + 'endpoints' => [ + 'user' => '/ocs/v2.php/cloud/users', + 'groups' => '/ocs/v2.php/cloud/groups', + 'apps' => '/ocs/v2.php/cloud/apps', + ], + ]; + } + + return new \OCP\AppFramework\Http\JSONResponse([ + 'version' => 2, + 'services' => $services, + ]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/OCS/Result.php b/docker/overlays/nextcloud/html/lib/private/OCS/Result.php new file mode 100644 index 0000000..0199783 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/OCS/Result.php @@ -0,0 +1,159 @@ + + * @author Björn Schießle + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Tom Needham + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\OCS; + +class Result { + + /** @var array */ + protected $data; + + /** @var null|string */ + protected $message; + + /** @var int */ + protected $statusCode; + + /** @var integer */ + protected $items; + + /** @var integer */ + protected $perPage; + + /** @var array */ + private $headers = []; + + /** + * create the OCS_Result object + * @param mixed $data the data to return + * @param int $code + * @param null|string $message + * @param array $headers + */ + public function __construct($data = null, $code = 100, $message = null, $headers = []) { + if ($data === null) { + $this->data = []; + } elseif (!is_array($data)) { + $this->data = [$this->data]; + } else { + $this->data = $data; + } + $this->statusCode = $code; + $this->message = $message; + $this->headers = $headers; + } + + /** + * optionally set the total number of items available + * @param int $items + */ + public function setTotalItems($items) { + $this->items = $items; + } + + /** + * optionally set the the number of items per page + * @param int $items + */ + public function setItemsPerPage($items) { + $this->perPage = $items; + } + + /** + * get the status code + * @return int + */ + public function getStatusCode() { + return $this->statusCode; + } + + /** + * get the meta data for the result + * @return array + */ + public function getMeta() { + $meta = []; + $meta['status'] = $this->succeeded() ? 'ok' : 'failure'; + $meta['statuscode'] = $this->statusCode; + $meta['message'] = $this->message; + if (isset($this->items)) { + $meta['totalitems'] = $this->items; + } + if (isset($this->perPage)) { + $meta['itemsperpage'] = $this->perPage; + } + return $meta; + } + + /** + * get the result data + * @return array + */ + public function getData() { + return $this->data; + } + + /** + * return bool Whether the method succeeded + * @return bool + */ + public function succeeded() { + return ($this->statusCode == 100); + } + + /** + * Adds a new header to the response + * @param string $name The name of the HTTP header + * @param string $value The value, null will delete it + * @return $this + */ + public function addHeader($name, $value) { + $name = trim($name); // always remove leading and trailing whitespace + // to be able to reliably check for security + // headers + + if (is_null($value)) { + unset($this->headers[$name]); + } else { + $this->headers[$name] = $value; + } + + return $this; + } + + /** + * Returns the set headers + * @return array the headers + */ + public function getHeaders() { + return $this->headers; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/BMP.php b/docker/overlays/nextcloud/html/lib/private/Preview/BMP.php new file mode 100644 index 0000000..7c69d55 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/BMP.php @@ -0,0 +1,33 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class BMP extends Image { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/bmp/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/BackgroundCleanupJob.php b/docker/overlays/nextcloud/html/lib/private/Preview/BackgroundCleanupJob.php new file mode 100644 index 0000000..e0546c4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/BackgroundCleanupJob.php @@ -0,0 +1,156 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OC\BackgroundJob\TimedJob; +use OC\Preview\Storage\Root; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IDBConnection; + +class BackgroundCleanupJob extends TimedJob { + + /** @var IDBConnection */ + private $connection; + + /** @var Root */ + private $previewFolder; + + /** @var bool */ + private $isCLI; + + /** @var IMimeTypeLoader */ + private $mimeTypeLoader; + + public function __construct(IDBConnection $connection, + Root $previewFolder, + IMimeTypeLoader $mimeTypeLoader, + bool $isCLI) { + // Run at most once an hour + $this->setInterval(3600); + + $this->connection = $connection; + $this->previewFolder = $previewFolder; + $this->isCLI = $isCLI; + $this->mimeTypeLoader = $mimeTypeLoader; + } + + public function run($argument) { + foreach ($this->getDeletedFiles() as $fileId) { + try { + $preview = $this->previewFolder->getFolder((string)$fileId); + $preview->delete(); + } catch (NotFoundException $e) { + // continue + } catch (NotPermittedException $e) { + // continue + } + } + } + + private function getDeletedFiles(): \Iterator { + yield from $this->getOldPreviewLocations(); + yield from $this->getNewPreviewLocations(); + } + + private function getOldPreviewLocations(): \Iterator { + $qb = $this->connection->getQueryBuilder(); + $qb->select('a.name') + ->from('filecache', 'a') + ->leftJoin('a', 'filecache', 'b', $qb->expr()->eq( + $qb->expr()->castColumn('a.name', IQueryBuilder::PARAM_INT), 'b.fileid' + )) + ->where( + $qb->expr()->isNull('b.fileid') + )->andWhere( + $qb->expr()->eq('a.parent', $qb->createNamedParameter($this->previewFolder->getId())) + )->andWhere( + $qb->expr()->like('a.name', $qb->createNamedParameter('__%')) + ); + + if (!$this->isCLI) { + $qb->setMaxResults(10); + } + + $cursor = $qb->execute(); + + while ($row = $cursor->fetch()) { + yield $row['name']; + } + + $cursor->closeCursor(); + } + + private function getNewPreviewLocations(): \Iterator { + $qb = $this->connection->getQueryBuilder(); + $qb->select('path', 'mimetype') + ->from('filecache') + ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($this->previewFolder->getId()))); + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === null) { + return []; + } + + /* + * This lovely like is the result of the way the new previews are stored + * We take the md5 of the name (fileid) and split the first 7 chars. That way + * there are not a gazillion files in the root of the preview appdata. + */ + $like = $this->connection->escapeLikeParameter($data['path']) . '/_/_/_/_/_/_/_/%'; + + $qb = $this->connection->getQueryBuilder(); + $qb->select('a.name') + ->from('filecache', 'a') + ->leftJoin('a', 'filecache', 'b', $qb->expr()->eq( + $qb->expr()->castColumn('a.name', IQueryBuilder::PARAM_INT), 'b.fileid' + )) + ->where( + $qb->expr()->andX( + $qb->expr()->isNull('b.fileid'), + $qb->expr()->like('a.path', $qb->createNamedParameter($like)), + $qb->expr()->eq('a.mimetype', $qb->createNamedParameter($this->mimeTypeLoader->getId('httpd/unix-directory'))) + ) + ); + + if (!$this->isCLI) { + $qb->setMaxResults(10); + } + + $cursor = $qb->execute(); + + while ($row = $cursor->fetch()) { + yield $row['name']; + } + + $cursor->closeCursor(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Bitmap.php b/docker/overlays/nextcloud/html/lib/private/Preview/Bitmap.php new file mode 100644 index 0000000..7322e07 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Bitmap.php @@ -0,0 +1,119 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use Imagick; +use OCP\Files\File; +use OCP\IImage; +use OCP\ILogger; + +/** + * Creates a PNG preview using ImageMagick via the PECL extension + * + * @package OC\Preview + */ +abstract class Bitmap extends ProviderV2 { + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $tmpPath = $this->getLocalFile($file); + + // Creates \Imagick object from bitmap or vector file + try { + $bp = $this->getResizedPreview($tmpPath, $maxX, $maxY); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => 'File: ' . $file->getPath() . ' Imagick says:', + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return null; + } + + $this->cleanTmpFiles(); + + //new bitmap image object + $image = new \OC_Image(); + $image->loadFromData($bp); + //check if image object is valid + return $image->valid() ? $image : null; + } + + /** + * Returns a preview of maxX times maxY dimensions in PNG format + * + * * The default resolution is already 72dpi, no need to change it for a bitmap output + * * It's possible to have proper colour conversion using profileimage(). + * ICC profiles are here: http://www.color.org/srgbprofiles.xalter + * * It's possible to Gamma-correct an image via gammaImage() + * + * @param string $tmpPath the location of the file to convert + * @param int $maxX + * @param int $maxY + * + * @return \Imagick + */ + private function getResizedPreview($tmpPath, $maxX, $maxY) { + $bp = new Imagick(); + + // Layer 0 contains either the bitmap or a flat representation of all vector layers + $bp->readImage($tmpPath . '[0]'); + + $bp = $this->resize($bp, $maxX, $maxY); + + $bp->setImageFormat('png'); + + return $bp; + } + + /** + * Returns a resized \Imagick object + * + * If you want to know more on the various methods available to resize an + * image, check out this link : @link https://stackoverflow.com/questions/8517304/what-the-difference-of-sample-resample-scale-resize-adaptive-resize-thumbnail-im + * + * @param \Imagick $bp + * @param int $maxX + * @param int $maxY + * + * @return \Imagick + */ + private function resize($bp, $maxX, $maxY) { + list($previewWidth, $previewHeight) = array_values($bp->getImageGeometry()); + + // We only need to resize a preview which doesn't fit in the maximum dimensions + if ($previewWidth > $maxX || $previewHeight > $maxY) { + // TODO: LANCZOS is the default filter, CATROM could bring similar results faster + $bp->resizeImage($maxX, $maxY, imagick::FILTER_LANCZOS, 1, true); + } + + return $bp; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Bundled.php b/docker/overlays/nextcloud/html/lib/private/Preview/Bundled.php new file mode 100644 index 0000000..f3441b5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Bundled.php @@ -0,0 +1,54 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OC\Archive\ZIP; +use OCP\Files\File; +use OCP\IImage; + +/** + * Extracts a preview from files that embed them in an ZIP archive + */ +abstract class Bundled extends ProviderV2 { + protected function extractThumbnail(File $file, $path): ?IImage { + $sourceTmp = \OC::$server->getTempManager()->getTemporaryFile(); + $targetTmp = \OC::$server->getTempManager()->getTemporaryFile(); + + try { + $content = $file->fopen('r'); + file_put_contents($sourceTmp, $content); + + $zip = new ZIP($sourceTmp); + $zip->extractFile($path, $targetTmp); + + $image = new \OC_Image(); + $image->loadFromFile($targetTmp); + $image->fixOrientation(); + + return $image; + } catch (\Exception $e) { + return null; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Font.php b/docker/overlays/nextcloud/html/lib/private/Preview/Font.php new file mode 100644 index 0000000..7f34dc0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Font.php @@ -0,0 +1,34 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +// .otf, .ttf and .pfb +class Font extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/(?:font-sfnt|x-font$)/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/GIF.php b/docker/overlays/nextcloud/html/lib/private/Preview/GIF.php new file mode 100644 index 0000000..bd9e569 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/GIF.php @@ -0,0 +1,33 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class GIF extends Image { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/gif/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Generator.php b/docker/overlays/nextcloud/html/lib/private/Preview/Generator.php new file mode 100644 index 0000000..2be08d1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Generator.php @@ -0,0 +1,494 @@ + + * + * @author Christoph Wurst + * @author Elijah Martin-Merrill + * @author John Molakvoæ (skjnldsv) + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Scott Dutton + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IConfig; +use OCP\IImage; +use OCP\IPreview; +use OCP\Preview\IProviderV2; +use OCP\Preview\IVersionedPreviewFile; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Generator { + + /** @var IPreview */ + private $previewManager; + /** @var IConfig */ + private $config; + /** @var IAppData */ + private $appData; + /** @var GeneratorHelper */ + private $helper; + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** + * @param IConfig $config + * @param IPreview $previewManager + * @param IAppData $appData + * @param GeneratorHelper $helper + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + IConfig $config, + IPreview $previewManager, + IAppData $appData, + GeneratorHelper $helper, + EventDispatcherInterface $eventDispatcher + ) { + $this->config = $config; + $this->previewManager = $previewManager; + $this->appData = $appData; + $this->helper = $helper; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * Returns a preview of a file + * + * The cache is searched first and if nothing usable was found then a preview is + * generated by one of the providers + * + * @param File $file + * @param int $width + * @param int $height + * @param bool $crop + * @param string $mode + * @param string $mimeType + * @return ISimpleFile + * @throws NotFoundException + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) + */ + public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) { + $specification = [ + 'width' => $width, + 'height' => $height, + 'crop' => $crop, + 'mode' => $mode, + ]; + $this->eventDispatcher->dispatch( + IPreview::EVENT, + new GenericEvent($file, $specification) + ); + + // since we only ask for one preview, and the generate method return the last one it created, it returns the one we want + return $this->generatePreviews($file, [$specification], $mimeType); + } + + /** + * Generates previews of a file + * + * @param File $file + * @param array $specifications + * @param string $mimeType + * @return ISimpleFile the last preview that was generated + * @throws NotFoundException + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) + */ + public function generatePreviews(File $file, array $specifications, $mimeType = null) { + //Make sure that we can read the file + if (!$file->isReadable()) { + throw new NotFoundException('Cannot read file'); + } + + if ($mimeType === null) { + $mimeType = $file->getMimeType(); + } + + $previewFolder = $this->getPreviewFolder($file); + + $previewVersion = ''; + if ($file instanceof IVersionedPreviewFile) { + $previewVersion = $file->getPreviewVersion() . '-'; + } + + // Get the max preview and infer the max preview sizes from that + $maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion); + $maxPreviewImage = null; // only load the image when we need it + if ($maxPreview->getSize() === 0) { + $maxPreview->delete(); + throw new NotFoundException('Max preview size 0, invalid!'); + } + + [$maxWidth, $maxHeight] = $this->getPreviewSize($maxPreview, $previewVersion); + + $preview = null; + + foreach ($specifications as $specification) { + $width = $specification['width'] ?? -1; + $height = $specification['height'] ?? -1; + $crop = $specification['crop'] ?? false; + $mode = $specification['mode'] ?? IPreview::MODE_FILL; + + // If both width and height are -1 we just want the max preview + if ($width === -1 && $height === -1) { + $width = $maxWidth; + $height = $maxHeight; + } + + // Calculate the preview size + [$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight); + + // No need to generate a preview that is just the max preview + if ($width === $maxWidth && $height === $maxHeight) { + // ensure correct return value if this was the last one + $preview = $maxPreview; + continue; + } + + // Try to get a cached preview. Else generate (and store) one + try { + try { + $preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion); + } catch (NotFoundException $e) { + if (!$this->previewManager->isMimeSupported($mimeType)) { + throw new NotFoundException(); + } + + if ($maxPreviewImage === null) { + $maxPreviewImage = $this->helper->getImage($maxPreview); + } + + $preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion); + } + } catch (\InvalidArgumentException $e) { + throw new NotFoundException("", 0, $e); + } + + if ($preview->getSize() === 0) { + $preview->delete(); + throw new NotFoundException('Cached preview size 0, invalid!'); + } + } + + // Free memory being used by the embedded image resource. Without this the image is kept in memory indefinitely. + // Garbage Collection does NOT free this memory. We have to do it ourselves. + if ($maxPreviewImage instanceof \OC_Image) { + $maxPreviewImage->destroy(); + } + + return $preview; + } + + /** + * @param ISimpleFolder $previewFolder + * @param File $file + * @param string $mimeType + * @param string $prefix + * @return ISimpleFile + * @throws NotFoundException + */ + private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeType, $prefix) { + $nodes = $previewFolder->getDirectoryListing(); + + foreach ($nodes as $node) { + $name = $node->getName(); + if (($prefix === '' || strpos($name, $prefix) === 0) && strpos($name, 'max')) { + return $node; + } + } + + $previewProviders = $this->previewManager->getProviders(); + foreach ($previewProviders as $supportedMimeType => $providers) { + if (!preg_match($supportedMimeType, $mimeType)) { + continue; + } + + foreach ($providers as $providerClosure) { + $provider = $this->helper->getProvider($providerClosure); + if (!($provider instanceof IProviderV2)) { + continue; + } + + if (!$provider->isAvailable($file)) { + continue; + } + + $maxWidth = (int)$this->config->getSystemValue('preview_max_x', 4096); + $maxHeight = (int)$this->config->getSystemValue('preview_max_y', 4096); + + $preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight); + + if (!($preview instanceof IImage)) { + continue; + } + + // Try to get the extention. + try { + $ext = $this->getExtention($preview->dataMimeType()); + } catch (\InvalidArgumentException $e) { + // Just continue to the next iteration if this preview doesn't have a valid mimetype + continue; + } + + $path = $prefix . (string)$preview->width() . '-' . (string)$preview->height() . '-max.' . $ext; + try { + $file = $previewFolder->newFile($path); + $file->putContent($preview->data()); + } catch (NotPermittedException $e) { + throw new NotFoundException(); + } + + return $file; + } + } + + throw new NotFoundException(); + } + + /** + * @param ISimpleFile $file + * @param string $prefix + * @return int[] + */ + private function getPreviewSize(ISimpleFile $file, string $prefix = '') { + $size = explode('-', substr($file->getName(), strlen($prefix))); + return [(int)$size[0], (int)$size[1]]; + } + + /** + * @param int $width + * @param int $height + * @param bool $crop + * @param string $mimeType + * @param string $prefix + * @return string + */ + private function generatePath($width, $height, $crop, $mimeType, $prefix) { + $path = $prefix . (string)$width . '-' . (string)$height; + if ($crop) { + $path .= '-crop'; + } + + $ext = $this->getExtention($mimeType); + $path .= '.' . $ext; + return $path; + } + + + /** + * @param int $width + * @param int $height + * @param bool $crop + * @param string $mode + * @param int $maxWidth + * @param int $maxHeight + * @return int[] + */ + private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight) { + + /* + * If we are not cropping we have to make sure the requested image + * respects the aspect ratio of the original. + */ + if (!$crop) { + $ratio = $maxHeight / $maxWidth; + + if ($width === -1) { + $width = $height / $ratio; + } + if ($height === -1) { + $height = $width * $ratio; + } + + $ratioH = $height / $maxHeight; + $ratioW = $width / $maxWidth; + + /* + * Fill means that the $height and $width are the max + * Cover means min. + */ + if ($mode === IPreview::MODE_FILL) { + if ($ratioH > $ratioW) { + $height = $width * $ratio; + } else { + $width = $height / $ratio; + } + } elseif ($mode === IPreview::MODE_COVER) { + if ($ratioH > $ratioW) { + $width = $height / $ratio; + } else { + $height = $width * $ratio; + } + } + } + + if ($height !== $maxHeight && $width !== $maxWidth) { + /* + * Scale to the nearest power of four + */ + $pow4height = 4 ** ceil(log($height) / log(4)); + $pow4width = 4 ** ceil(log($width) / log(4)); + + // Minimum size is 64 + $pow4height = max($pow4height, 64); + $pow4width = max($pow4width, 64); + + $ratioH = $height / $pow4height; + $ratioW = $width / $pow4width; + + if ($ratioH < $ratioW) { + $width = $pow4width; + $height /= $ratioW; + } else { + $height = $pow4height; + $width /= $ratioH; + } + } + + /* + * Make sure the requested height and width fall within the max + * of the preview. + */ + if ($height > $maxHeight) { + $ratio = $height / $maxHeight; + $height = $maxHeight; + $width /= $ratio; + } + if ($width > $maxWidth) { + $ratio = $width / $maxWidth; + $width = $maxWidth; + $height /= $ratio; + } + + return [(int)round($width), (int)round($height)]; + } + + /** + * @param ISimpleFolder $previewFolder + * @param ISimpleFile $maxPreview + * @param int $width + * @param int $height + * @param bool $crop + * @param int $maxWidth + * @param int $maxHeight + * @param string $prefix + * @return ISimpleFile + * @throws NotFoundException + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) + */ + private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) { + $preview = $maxPreview; + if (!$preview->valid()) { + throw new \InvalidArgumentException('Failed to generate preview, failed to load image'); + } + + if ($crop) { + if ($height !== $preview->height() && $width !== $preview->width()) { + //Resize + $widthR = $preview->width() / $width; + $heightR = $preview->height() / $height; + + if ($widthR > $heightR) { + $scaleH = $height; + $scaleW = $maxWidth / $heightR; + } else { + $scaleH = $maxHeight / $widthR; + $scaleW = $width; + } + $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH)); + } + $cropX = (int)floor(abs($width - $preview->width()) * 0.5); + $cropY = (int)floor(abs($height - $preview->height()) * 0.5); + $preview = $preview->cropCopy($cropX, $cropY, $width, $height); + } else { + $preview = $maxPreview->resizeCopy(max($width, $height)); + } + + + $path = $this->generatePath($width, $height, $crop, $preview->dataMimeType(), $prefix); + try { + $file = $previewFolder->newFile($path); + $file->putContent($preview->data()); + } catch (NotPermittedException $e) { + throw new NotFoundException(); + } + + return $file; + } + + /** + * @param ISimpleFolder $previewFolder + * @param int $width + * @param int $height + * @param bool $crop + * @param string $mimeType + * @param string $prefix + * @return ISimpleFile + * + * @throws NotFoundException + */ + private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop, $mimeType, $prefix) { + $path = $this->generatePath($width, $height, $crop, $mimeType, $prefix); + + return $previewFolder->getFile($path); + } + + /** + * Get the specific preview folder for this file + * + * @param File $file + * @return ISimpleFolder + */ + private function getPreviewFolder(File $file) { + try { + $folder = $this->appData->getFolder($file->getId()); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder($file->getId()); + } + + return $folder; + } + + /** + * @param string $mimeType + * @return null|string + * @throws \InvalidArgumentException + */ + private function getExtention($mimeType) { + switch ($mimeType) { + case 'image/png': + return 'png'; + case 'image/jpeg': + return 'jpg'; + case 'image/gif': + return 'gif'; + default: + throw new \InvalidArgumentException('Not a valid mimetype: "' . $mimeType . '"'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/GeneratorHelper.php b/docker/overlays/nextcloud/html/lib/private/Preview/GeneratorHelper.php new file mode 100644 index 0000000..843aa0e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/GeneratorHelper.php @@ -0,0 +1,87 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\IConfig; +use OCP\IImage; +use OCP\Image as OCPImage; +use OCP\Preview\IProvider; +use OCP\Preview\IProviderV2; + +/** + * Very small wrapper class to make the generator fully unit testable + */ +class GeneratorHelper { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var IConfig */ + private $config; + + public function __construct(IRootFolder $rootFolder, IConfig $config) { + $this->rootFolder = $rootFolder; + $this->config = $config; + } + + /** + * @param IProviderV2 $provider + * @param File $file + * @param int $maxWidth + * @param int $maxHeight + * + * @return bool|IImage + */ + public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight) { + return $provider->getThumbnail($file, $maxWidth, $maxHeight); + } + + /** + * @param ISimpleFile $maxPreview + * @return IImage + */ + public function getImage(ISimpleFile $maxPreview) { + $image = new OCPImage(); + $image->loadFromData($maxPreview->getContent()); + return $image; + } + + /** + * @param callable $providerClosure + * @return IProviderV2 + */ + public function getProvider($providerClosure) { + $provider = $providerClosure(); + if ($provider instanceof IProvider) { + $provider = new ProviderV1Adapter($provider); + } + return $provider; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/HEIC.php b/docker/overlays/nextcloud/html/lib/private/Preview/HEIC.php new file mode 100644 index 0000000..c2b9b54 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/HEIC.php @@ -0,0 +1,144 @@ + + * @author Roeland Jago Douma + * @author Sebastian Steinmetz <462714+steiny2k@users.noreply.github.com> + * @author Sebastian Steinmetz + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; +use OCP\ILogger; + +/** + * Creates a JPG preview using ImageMagick via the PECL extension + * + * @package OC\Preview + */ +class HEIC extends ProviderV2 { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/hei(f|c)/'; + } + + /** + * {@inheritDoc} + */ + public function isAvailable(\OCP\Files\FileInfo $file): bool { + return in_array('HEIC', \Imagick::queryFormats("HEI*")); + } + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $tmpPath = $this->getLocalFile($file); + + // Creates \Imagick object from the heic file + try { + $bp = $this->getResizedPreview($tmpPath, $maxX, $maxY); + $bp->setFormat('jpg'); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => 'File: ' . $file->getPath() . ' Imagick says:', + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return null; + } + + $this->cleanTmpFiles(); + + //new bitmap image object + $image = new \OC_Image(); + $image->loadFromData($bp); + //check if image object is valid + return $image->valid() ? $image : null; + } + + /** + * Returns a preview of maxX times maxY dimensions in JPG format + * + * * The default resolution is already 72dpi, no need to change it for a bitmap output + * * It's possible to have proper colour conversion using profileimage(). + * ICC profiles are here: http://www.color.org/srgbprofiles.xalter + * * It's possible to Gamma-correct an image via gammaImage() + * + * @param string $tmpPath the location of the file to convert + * @param int $maxX + * @param int $maxY + * + * @return \Imagick + */ + private function getResizedPreview($tmpPath, $maxX, $maxY) { + $bp = new \Imagick(); + + // Layer 0 contains either the bitmap or a flat representation of all vector layers + $bp->readImage($tmpPath . '[0]'); + + $bp->setImageFormat('jpg'); + + $bp = $this->resize($bp, $maxX, $maxY); + + return $bp; + } + + /** + * Returns a resized \Imagick object + * + * If you want to know more on the various methods available to resize an + * image, check out this link : @link https://stackoverflow.com/questions/8517304/what-the-difference-of-sample-resample-scale-resize-adaptive-resize-thumbnail-im + * + * @param \Imagick $bp + * @param int $maxX + * @param int $maxY + * + * @return \Imagick + */ + private function resize($bp, $maxX, $maxY) { + list($previewWidth, $previewHeight) = array_values($bp->getImageGeometry()); + + // We only need to resize a preview which doesn't fit in the maximum dimensions + if ($previewWidth > $maxX || $previewHeight > $maxY) { + // If we want a small image (thumbnail) let's be most space- and time-efficient + if ($maxX <= 500 && $maxY <= 500) { + $bp->thumbnailImage($maxY, $maxX, true); + $bp->stripImage(); + } else { + // A bigger image calls for some better resizing algorithm + // According to http://www.imagemagick.org/Usage/filter/#lanczos + // the catrom filter is almost identical to Lanczos2, but according + // to http://php.net/manual/en/imagick.resizeimage.php it is + // significantly faster + $bp->resizeImage($maxX, $maxY, \Imagick::FILTER_CATROM, 1, true); + } + } + + return $bp; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Illustrator.php b/docker/overlays/nextcloud/html/lib/private/Preview/Illustrator.php new file mode 100644 index 0000000..20c3552 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Illustrator.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.ai +class Illustrator extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/illustrator/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Image.php b/docker/overlays/nextcloud/html/lib/private/Preview/Image.php new file mode 100644 index 0000000..eea471a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Image.php @@ -0,0 +1,64 @@ + + * @author Joas Schilling + * @author josh4trunks + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; + +abstract class Image extends ProviderV2 { + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $maxSizeForImages = \OC::$server->getConfig()->getSystemValue('preview_max_filesize_image', 50); + $size = $file->getSize(); + + if ($maxSizeForImages !== -1 && $size > ($maxSizeForImages * 1024 * 1024)) { + return null; + } + + $image = new \OC_Image(); + + $fileName = $this->getLocalFile($file); + + $image->loadFromFile($fileName); + $image->fixOrientation(); + + $this->cleanTmpFiles(); + + if ($image->valid()) { + $image->scaleDownToFit($maxX, $maxY); + + return $image; + } + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/JPEG.php b/docker/overlays/nextcloud/html/lib/private/Preview/JPEG.php new file mode 100644 index 0000000..abdb6b6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/JPEG.php @@ -0,0 +1,33 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class JPEG extends Image { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/jpeg/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Krita.php b/docker/overlays/nextcloud/html/lib/private/Preview/Krita.php new file mode 100644 index 0000000..b14d1a3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Krita.php @@ -0,0 +1,52 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; + +class Krita extends Bundled { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/x-krita/'; + } + + + /** + * @inheritDoc + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $image = $this->extractThumbnail($file, 'mergedimage.png'); + if ($image->valid()) { + return $image; + } + $image = $this->extractThumbnail($file, 'preview.png'); + if ($image->valid()) { + return $image; + } + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/MP3.php b/docker/overlays/nextcloud/html/lib/private/Preview/MP3.php new file mode 100644 index 0000000..868078a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/MP3.php @@ -0,0 +1,72 @@ + + * @author Georg Ehrke + * @author Joas Schilling + * @author Lukas Reschke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use ID3Parser\ID3Parser; + +use OCP\Files\File; +use OCP\IImage; + +class MP3 extends ProviderV2 { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/audio\/mpeg/'; + } + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $getID3 = new ID3Parser(); + + $tmpPath = $this->getLocalFile($file); + $tags = $getID3->analyze($tmpPath); + $this->cleanTmpFiles(); + $picture = isset($tags['id3v2']['APIC'][0]['data']) ? $tags['id3v2']['APIC'][0]['data'] : null; + if (is_null($picture) && isset($tags['id3v2']['PIC'][0]['data'])) { + $picture = $tags['id3v2']['PIC'][0]['data']; + } + + if (!is_null($picture)) { + $image = new \OC_Image(); + $image->loadFromData($picture); + + if ($image->valid()) { + $image->scaleDownToFit($maxX, $maxY); + + return $image; + } + } + + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2003.php b/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2003.php new file mode 100644 index 0000000..76781e1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2003.php @@ -0,0 +1,35 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.docm, .dotm, .xls(m), .xlt(m), .xla(m), .ppt(m), .pot(m), .pps(m), .ppa(m) +class MSOffice2003 extends Office { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/vnd.ms-.*/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2007.php b/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2007.php new file mode 100644 index 0000000..bd1e53d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/MSOffice2007.php @@ -0,0 +1,35 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.docx, .dotx, .xlsx, .xltx, .pptx, .potx, .ppsx +class MSOffice2007 extends Office { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/vnd.openxmlformats-officedocument.*/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/MSOfficeDoc.php b/docker/overlays/nextcloud/html/lib/private/Preview/MSOfficeDoc.php new file mode 100644 index 0000000..2068de6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/MSOfficeDoc.php @@ -0,0 +1,35 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.doc, .dot +class MSOfficeDoc extends Office { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/msword/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/MarkDown.php b/docker/overlays/nextcloud/html/lib/private/Preview/MarkDown.php new file mode 100644 index 0000000..91e276e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/MarkDown.php @@ -0,0 +1,34 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class MarkDown extends TXT { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/text\/(x-)?markdown/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Movie.php b/docker/overlays/nextcloud/html/lib/private/Preview/Movie.php new file mode 100644 index 0000000..68157f1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Movie.php @@ -0,0 +1,106 @@ + + * @author Daniel Schneider + * @author Georg Ehrke + * @author Joas Schilling + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; + +class Movie extends ProviderV2 { + public static $avconvBinary; + public static $ffmpegBinary; + + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/video\/.*/'; + } + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + // TODO: use proc_open() and stream the source file ? + + $absPath = $this->getLocalFile($file, 5242880); // only use the first 5MB + + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 5); + if ($result === null) { + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 1); + if ($result === null) { + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 0); + } + } + + $this->cleanTmpFiles(); + + return $result; + } + + /** + * @param int $maxX + * @param int $maxY + * @param string $absPath + * @param int $second + * @return null|\OCP\IImage + */ + private function generateThumbNail($maxX, $maxY, $absPath, $second): ?IImage { + $tmpPath = \OC::$server->getTempManager()->getTemporaryFile(); + + if (self::$avconvBinary) { + $cmd = self::$avconvBinary . ' -y -ss ' . escapeshellarg($second) . + ' -i ' . escapeshellarg($absPath) . + ' -an -f mjpeg -vframes 1 -vsync 1 ' . escapeshellarg($tmpPath) . + ' > /dev/null 2>&1'; + } else { + $cmd = self::$ffmpegBinary . ' -y -ss ' . escapeshellarg($second) . + ' -i ' . escapeshellarg($absPath) . + ' -f mjpeg -vframes 1' . + ' ' . escapeshellarg($tmpPath) . + ' > /dev/null 2>&1'; + } + + exec($cmd, $output, $returnCode); + + if ($returnCode === 0) { + $image = new \OC_Image(); + $image->loadFromFile($tmpPath); + if ($image->valid()) { + unlink($tmpPath); + $image->scaleDownToFit($maxX, $maxY); + + return $image; + } + } + unlink($tmpPath); + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Office.php b/docker/overlays/nextcloud/html/lib/private/Preview/Office.php new file mode 100644 index 0000000..6719aea --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Office.php @@ -0,0 +1,115 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Tor Lillqvist + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; +use OCP\ILogger; + +abstract class Office extends ProviderV2 { + private $cmd; + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $this->initCmd(); + if (is_null($this->cmd)) { + return null; + } + + $absPath = $this->getLocalFile($file); + + $tmpDir = \OC::$server->getTempManager()->getTempBaseDir(); + + $defaultParameters = ' -env:UserInstallation=file://' . escapeshellarg($tmpDir . '/owncloud-' . \OC_Util::getInstanceId() . '/') . ' --headless --nologo --nofirststartwizard --invisible --norestore --convert-to png --outdir '; + $clParameters = \OC::$server->getConfig()->getSystemValue('preview_office_cl_parameters', $defaultParameters); + + $exec = $this->cmd . $clParameters . escapeshellarg($tmpDir) . ' ' . escapeshellarg($absPath); + + shell_exec($exec); + + //create imagick object from png + $pngPreview = null; + try { + list($dirname, , , $filename) = array_values(pathinfo($absPath)); + $pngPreview = $tmpDir . '/' . $filename . '.png'; + + $png = new \imagick($pngPreview . '[0]'); + $png->setImageFormat('jpg'); + } catch (\Exception $e) { + $this->cleanTmpFiles(); + unlink($pngPreview); + \OC::$server->getLogger()->logException($e, [ + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return null; + } + + $image = new \OC_Image(); + $image->loadFromData($png); + + $this->cleanTmpFiles(); + unlink($pngPreview); + + if ($image->valid()) { + $image->scaleDownToFit($maxX, $maxY); + + return $image; + } + return null; + } + + private function initCmd() { + $cmd = ''; + + $libreOfficePath = \OC::$server->getConfig()->getSystemValue('preview_libreoffice_path', null); + if (is_string($libreOfficePath)) { + $cmd = $libreOfficePath; + } + + $whichLibreOffice = shell_exec('command -v libreoffice'); + if ($cmd === '' && !empty($whichLibreOffice)) { + $cmd = 'libreoffice'; + } + + $whichOpenOffice = shell_exec('command -v openoffice'); + if ($cmd === '' && !empty($whichOpenOffice)) { + $cmd = 'openoffice'; + } + + if ($cmd === '') { + $cmd = null; + } + + $this->cmd = $cmd; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/OpenDocument.php b/docker/overlays/nextcloud/html/lib/private/Preview/OpenDocument.php new file mode 100644 index 0000000..01e191c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/OpenDocument.php @@ -0,0 +1,51 @@ + + * @author Julius Härtl + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.odt, .ott, .oth, .odm, .odg, .otg, .odp, .otp, .ods, .ots, .odc, .odf, .odb, .odi, .oxt +use OCP\Files\File; +use OCP\IImage; + +class OpenDocument extends Bundled { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/vnd.oasis.opendocument.*/'; + } + + + /** + * @inheritDoc + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $image = $this->extractThumbnail($file, 'Thumbnails/thumbnail.png'); + if ($image->valid()) { + return $image; + } + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/PDF.php b/docker/overlays/nextcloud/html/lib/private/Preview/PDF.php new file mode 100644 index 0000000..cc341be --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/PDF.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.pdf +class PDF extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/pdf/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/PNG.php b/docker/overlays/nextcloud/html/lib/private/Preview/PNG.php new file mode 100644 index 0000000..a10a170 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/PNG.php @@ -0,0 +1,33 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class PNG extends Image { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/png/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Photoshop.php b/docker/overlays/nextcloud/html/lib/private/Preview/Photoshop.php new file mode 100644 index 0000000..2f2eb5f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Photoshop.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.psd +class Photoshop extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/x-photoshop/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Postscript.php b/docker/overlays/nextcloud/html/lib/private/Preview/Postscript.php new file mode 100644 index 0000000..e651048 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Postscript.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.eps +class Postscript extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/postscript/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Provider.php b/docker/overlays/nextcloud/html/lib/private/Preview/Provider.php new file mode 100644 index 0000000..b8490fb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Provider.php @@ -0,0 +1,70 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Preview\IProvider; + +abstract class Provider implements IProvider { + private $options; + + /** + * Constructor + * + * @param array $options + */ + public function __construct(array $options = []) { + $this->options = $options; + } + + /** + * @return string Regex with the mimetypes that are supported by this provider + */ + abstract public function getMimeType(); + + /** + * Check if a preview can be generated for $path + * + * @param \OCP\Files\FileInfo $file + * @return bool + */ + public function isAvailable(\OCP\Files\FileInfo $file) { + return true; + } + + /** + * Generates thumbnail which fits in $maxX and $maxY and keeps the aspect ratio, for file at path $path + * + * @param string $path Path of file + * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image + * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image + * @param bool $scalingup Disable/Enable upscaling of previews + * @param \OC\Files\View $fileview fileview object of user folder + * @return bool|\OCP\IImage false if no preview was generated + */ + abstract public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV1Adapter.php b/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV1Adapter.php new file mode 100644 index 0000000..f09b0f4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV1Adapter.php @@ -0,0 +1,64 @@ + + * + * @author Julius Härtl + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OC\Files\View; +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\IImage; +use OCP\Preview\IProvider; +use OCP\Preview\IProviderV2; + +class ProviderV1Adapter implements IProviderV2 { + private $providerV1; + + public function __construct(IProvider $providerV1) { + $this->providerV1 = $providerV1; + } + + public function getMimeType(): string { + return (string)$this->providerV1->getMimeType(); + } + + public function isAvailable(FileInfo $file): bool { + return (bool)$this->providerV1->isAvailable($file); + } + + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + list($view, $path) = $this->getViewAndPath($file); + $thumbnail = $this->providerV1->getThumbnail($path, $maxX, $maxY, false, $view); + return $thumbnail === false ? null: $thumbnail; + } + + private function getViewAndPath(File $file) { + $view = new View($file->getParent()->getPath()); + $path = $file->getName(); + + return [$view, $path]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV2.php b/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV2.php new file mode 100644 index 0000000..5a36963 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/ProviderV2.php @@ -0,0 +1,110 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\IImage; +use OCP\Preview\IProviderV2; + +abstract class ProviderV2 implements IProviderV2 { + private $options; + + private $tmpFiles = []; + + /** + * Constructor + * + * @param array $options + */ + public function __construct(array $options = []) { + $this->options = $options; + } + + /** + * @return string Regex with the mimetypes that are supported by this provider + */ + abstract public function getMimeType(): string ; + + /** + * Check if a preview can be generated for $path + * + * @param FileInfo $file + * @return bool + */ + public function isAvailable(FileInfo $file): bool { + return true; + } + + /** + * get thumbnail for file at path $path + * + * @param File $file + * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image + * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image + * @return null|\OCP\IImage false if no preview was generated + * @since 17.0.0 + */ + abstract public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage; + + /** + * Get a path to either the local file or temporary file + * + * @param File $file + * @param int $maxSize maximum size for temporary files + * @return string + */ + protected function getLocalFile(File $file, int $maxSize = null): string { + $useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal(); + if ($useTempFile) { + $absPath = \OC::$server->getTempManager()->getTemporaryFile(); + + $content = $file->fopen('r'); + + if ($maxSize) { + $content = stream_get_contents($content, $maxSize); + } + + file_put_contents($absPath, $content); + $this->tmpFiles[] = $absPath; + return $absPath; + } else { + return $file->getStorage()->getLocalFile($file->getInternalPath()); + } + } + + /** + * Clean any generated temporary files + */ + protected function cleanTmpFiles() { + foreach ($this->tmpFiles as $tmpFile) { + unlink($tmpFile); + } + + $this->tmpFiles = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/SVG.php b/docker/overlays/nextcloud/html/lib/private/Preview/SVG.php new file mode 100644 index 0000000..6630dc2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/SVG.php @@ -0,0 +1,82 @@ + + * @author Georg Ehrke + * @author Joas Schilling + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\IImage; +use OCP\ILogger; + +class SVG extends ProviderV2 { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/svg\+xml/'; + } + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + try { + $svg = new \Imagick(); + $svg->setBackgroundColor(new \ImagickPixel('transparent')); + + $content = stream_get_contents($file->fopen('r')); + if (substr($content, 0, 5) !== '' . $content; + } + + // Do not parse SVG files with references + if (stripos($content, 'xlink:href') !== false) { + return null; + } + + $svg->readImageBlob($content); + $svg->setImageFormat('png32'); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return null; + } + + //new image object + $image = new \OC_Image(); + $image->loadFromData($svg); + //check if image object is valid + if ($image->valid()) { + $image->scaleDownToFit($maxX, $maxY); + + return $image; + } + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/StarOffice.php b/docker/overlays/nextcloud/html/lib/private/Preview/StarOffice.php new file mode 100644 index 0000000..fd17685 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/StarOffice.php @@ -0,0 +1,35 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.sxw, .stw, .sxc, .stc, .sxd, .std, .sxi, .sti, .sxg, .sxm +class StarOffice extends Office { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/application\/vnd.sun.xml.*/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Storage/Root.php b/docker/overlays/nextcloud/html/lib/private/Preview/Storage/Root.php new file mode 100644 index 0000000..675263f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Storage/Root.php @@ -0,0 +1,89 @@ + + * + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview\Storage; + +use OC\Files\AppData\AppData; +use OC\SystemConfig; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFolder; + +class Root extends AppData { + private $isMultibucketPreviewDistributionEnabled = false; + public function __construct(IRootFolder $rootFolder, SystemConfig $systemConfig) { + parent::__construct($rootFolder, $systemConfig, 'preview'); + + $this->isMultibucketPreviewDistributionEnabled = $systemConfig->getValue('objectstore.multibucket.preview-distribution', false) === true; + } + + + public function getFolder(string $name): ISimpleFolder { + $internalFolder = self::getInternalFolder($name); + + try { + return parent::getFolder($internalFolder); + } catch (NotFoundException $e) { + /* + * The new folder structure is not found. + * Lets try the old one + */ + } + + try { + return parent::getFolder($name); + } catch (NotFoundException $e) { + /* + * The old folder structure is not found. + * Lets try the multibucket fallback if available + */ + if ($this->isMultibucketPreviewDistributionEnabled) { + return parent::getFolder('old-multibucket/' . $internalFolder); + } + + // when there is no further fallback just throw the exception + throw $e; + } + } + + public function newFolder(string $name): ISimpleFolder { + $internalFolder = self::getInternalFolder($name); + return parent::newFolder($internalFolder); + } + + /* + * Do not allow directory listing on this special root + * since it gets to big and time consuming + */ + public function getDirectoryListing(): array { + return []; + } + + public static function getInternalFolder(string $name): string { + return implode('/', str_split(substr(md5($name), 0, 7))) . '/' . $name; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/TIFF.php b/docker/overlays/nextcloud/html/lib/private/Preview/TIFF.php new file mode 100644 index 0000000..20d5552 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/TIFF.php @@ -0,0 +1,35 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +//.tiff +class TIFF extends Bitmap { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/tiff/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/TXT.php b/docker/overlays/nextcloud/html/lib/private/Preview/TXT.php new file mode 100644 index 0000000..4004b0a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/TXT.php @@ -0,0 +1,108 @@ + + * @author Georg Ehrke + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author Morris Jobke + * @author Nmz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\IImage; + +class TXT extends ProviderV2 { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/text\/plain/'; + } + + /** + * {@inheritDoc} + */ + public function isAvailable(FileInfo $file): bool { + return $file->getSize() > 0; + } + + /** + * {@inheritDoc} + */ + public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { + $content = $file->fopen('r'); + + if ($content === false) { + return null; + } + + $content = stream_get_contents($content,3000); + + //don't create previews of empty text files + if (trim($content) === '') { + return null; + } + + $lines = preg_split("/\r\n|\n|\r/", $content); + + // Define text size of text file preview + $fontSize = $maxX ? (int) ((1 / 32) * $maxX) : 5; //5px + $lineSize = ceil($fontSize * 1.5); + + $image = imagecreate($maxX, $maxY); + imagecolorallocate($image, 255, 255, 255); + $textColor = imagecolorallocate($image, 0, 0, 0); + + $fontFile = __DIR__; + $fontFile .= '/../../../core'; + $fontFile .= '/fonts/NotoSans-Regular.ttf'; + + $canUseTTF = function_exists('imagettftext'); + + foreach ($lines as $index => $line) { + $index = $index + 1; + + $x = (int) 1; + $y = (int) ($index * $lineSize); + + if ($canUseTTF === true) { + imagettftext($image, $fontSize, 0, $x, $y, $textColor, $fontFile, $line); + } else { + $y -= $fontSize; + imagestring($image, 1, $x, $y, $line, $textColor); + } + + if (($index * $lineSize) >= $maxY) { + break; + } + } + + $imageObject = new \OC_Image(); + $imageObject->setResource($image); + + return $imageObject->valid() ? $imageObject : null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/Watcher.php b/docker/overlays/nextcloud/html/lib/private/Preview/Watcher.php new file mode 100644 index 0000000..c686b8e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/Watcher.php @@ -0,0 +1,77 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OCP\Files\Folder; +use OCP\Files\IAppData; +use OCP\Files\Node; +use OCP\Files\NotFoundException; + +/** + * Class Watcher + * + * @package OC\Preview + * + * Class that will watch filesystem activity and remove previews as needed. + */ +class Watcher { + /** @var IAppData */ + private $appData; + + /** + * Watcher constructor. + * + * @param IAppData $appData + */ + public function __construct(IAppData $appData) { + $this->appData = $appData; + } + + public function postWrite(Node $node) { + $this->deleteNode($node); + } + + protected function deleteNode(Node $node) { + // We only handle files + if ($node instanceof Folder) { + return; + } + + try { + $folder = $this->appData->getFolder((string)$node->getId()); + $folder->delete(); + } catch (NotFoundException $e) { + //Nothing to do + } + } + + public function versionRollback(array $data) { + if (isset($data['node'])) { + $this->deleteNode($data['node']); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/WatcherConnector.php b/docker/overlays/nextcloud/html/lib/private/Preview/WatcherConnector.php new file mode 100644 index 0000000..4d2d467 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/WatcherConnector.php @@ -0,0 +1,70 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Preview; + +use OC\SystemConfig; +use OCP\Files\IRootFolder; +use OCP\Files\Node; + +class WatcherConnector { + + /** @var IRootFolder */ + private $root; + + /** @var SystemConfig */ + private $config; + + /** + * WatcherConnector constructor. + * + * @param IRootFolder $root + * @param SystemConfig $config + */ + public function __construct(IRootFolder $root, + SystemConfig $config) { + $this->root = $root; + $this->config = $config; + } + + /** + * @return Watcher + */ + private function getWatcher(): Watcher { + return \OC::$server->query(Watcher::class); + } + + public function connectWatcher() { + // Do not connect if we are not setup yet! + if ($this->config->getValue('instanceid', null) !== null) { + $this->root->listen('\OC\Files', 'postWrite', function (Node $node) { + $this->getWatcher()->postWrite($node); + }); + + \OC_Hook::connect('\OCP\Versions', 'rollback', $this->getWatcher(), 'versionRollback'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Preview/XBitmap.php b/docker/overlays/nextcloud/html/lib/private/Preview/XBitmap.php new file mode 100644 index 0000000..f549841 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Preview/XBitmap.php @@ -0,0 +1,33 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Preview; + +class XBitmap extends Image { + /** + * {@inheritDoc} + */ + public function getMimeType(): string { + return '/image\/x-xbitmap/'; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/PreviewManager.php b/docker/overlays/nextcloud/html/lib/private/PreviewManager.php new file mode 100644 index 0000000..f6f6671 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/PreviewManager.php @@ -0,0 +1,432 @@ + + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sebastian Steinmetz <462714+steiny2k@users.noreply.github.com> + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\Preview\Generator; +use OC\Preview\GeneratorHelper; +use OCP\Files\File; +use OCP\Files\IAppData; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\IConfig; +use OCP\IPreview; +use OCP\Preview\IProviderV2; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class PreviewManager implements IPreview { + /** @var IConfig */ + protected $config; + + /** @var IRootFolder */ + protected $rootFolder; + + /** @var IAppData */ + protected $appData; + + /** @var EventDispatcherInterface */ + protected $eventDispatcher; + + /** @var Generator */ + private $generator; + + /** @var GeneratorHelper */ + private $helper; + + /** @var bool */ + protected $providerListDirty = false; + + /** @var bool */ + protected $registeredCoreProviders = false; + + /** @var array */ + protected $providers = []; + + /** @var array mime type => support status */ + protected $mimeTypeSupportMap = []; + + /** @var array */ + protected $defaultProviders; + + /** @var string */ + protected $userId; + + /** + * PreviewManager constructor. + * + * @param IConfig $config + * @param IRootFolder $rootFolder + * @param IAppData $appData + * @param EventDispatcherInterface $eventDispatcher + * @param string $userId + */ + public function __construct(IConfig $config, + IRootFolder $rootFolder, + IAppData $appData, + EventDispatcherInterface $eventDispatcher, + GeneratorHelper $helper, + $userId) { + $this->config = $config; + $this->rootFolder = $rootFolder; + $this->appData = $appData; + $this->eventDispatcher = $eventDispatcher; + $this->helper = $helper; + $this->userId = $userId; + } + + /** + * In order to improve lazy loading a closure can be registered which will be + * called in case preview providers are actually requested + * + * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2 + * + * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider + * @param \Closure $callable + * @return void + */ + public function registerProvider($mimeTypeRegex, \Closure $callable) { + if (!$this->config->getSystemValue('enable_previews', true)) { + return; + } + + if (!isset($this->providers[$mimeTypeRegex])) { + $this->providers[$mimeTypeRegex] = []; + } + $this->providers[$mimeTypeRegex][] = $callable; + $this->providerListDirty = true; + } + + /** + * Get all providers + * @return array + */ + public function getProviders() { + if (!$this->config->getSystemValue('enable_previews', true)) { + return []; + } + + $this->registerCoreProviders(); + if ($this->providerListDirty) { + $keys = array_map('strlen', array_keys($this->providers)); + array_multisort($keys, SORT_DESC, $this->providers); + $this->providerListDirty = false; + } + + return $this->providers; + } + + /** + * Does the manager have any providers + * @return bool + */ + public function hasProviders() { + $this->registerCoreProviders(); + return !empty($this->providers); + } + + private function getGenerator(): Generator { + if ($this->generator === null) { + $this->generator = new Generator( + $this->config, + $this, + $this->appData, + new GeneratorHelper( + $this->rootFolder, + $this->config + ), + $this->eventDispatcher + ); + } + return $this->generator; + } + + /** + * Returns a preview of a file + * + * The cache is searched first and if nothing usable was found then a preview is + * generated by one of the providers + * + * @param File $file + * @param int $width + * @param int $height + * @param bool $crop + * @param string $mode + * @param string $mimeType + * @return ISimpleFile + * @throws NotFoundException + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) + * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0 + */ + public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) { + return $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType); + } + + /** + * Generates previews of a file + * + * @param File $file + * @param array $specifications + * @param string $mimeType + * @return ISimpleFile the last preview that was generated + * @throws NotFoundException + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) + * @since 19.0.0 + */ + public function generatePreviews(File $file, array $specifications, $mimeType = null) { + return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType); + } + + /** + * returns true if the passed mime type is supported + * + * @param string $mimeType + * @return boolean + */ + public function isMimeSupported($mimeType = '*') { + if (!$this->config->getSystemValue('enable_previews', true)) { + return false; + } + + if (isset($this->mimeTypeSupportMap[$mimeType])) { + return $this->mimeTypeSupportMap[$mimeType]; + } + + $this->registerCoreProviders(); + $providerMimeTypes = array_keys($this->providers); + foreach ($providerMimeTypes as $supportedMimeType) { + if (preg_match($supportedMimeType, $mimeType)) { + $this->mimeTypeSupportMap[$mimeType] = true; + return true; + } + } + $this->mimeTypeSupportMap[$mimeType] = false; + return false; + } + + /** + * Check if a preview can be generated for a file + * + * @param \OCP\Files\FileInfo $file + * @return bool + */ + public function isAvailable(\OCP\Files\FileInfo $file) { + if (!$this->config->getSystemValue('enable_previews', true)) { + return false; + } + + $this->registerCoreProviders(); + if (!$this->isMimeSupported($file->getMimetype())) { + return false; + } + + $mount = $file->getMountPoint(); + if ($mount and !$mount->getOption('previews', true)) { + return false; + } + + foreach ($this->providers as $supportedMimeType => $providers) { + if (preg_match($supportedMimeType, $file->getMimetype())) { + foreach ($providers as $providerClosure) { + $provider = $this->helper->getProvider($providerClosure); + if (!($provider instanceof IProviderV2)) { + continue; + } + + if ($provider->isAvailable($file)) { + return true; + } + } + } + } + return false; + } + + /** + * List of enabled default providers + * + * The following providers are enabled by default: + * - OC\Preview\PNG + * - OC\Preview\JPEG + * - OC\Preview\GIF + * - OC\Preview\BMP + * - OC\Preview\HEIC + * - OC\Preview\XBitmap + * - OC\Preview\MarkDown + * - OC\Preview\MP3 + * - OC\Preview\TXT + * + * The following providers are disabled by default due to performance or privacy concerns: + * - OC\Preview\Font + * - OC\Preview\Illustrator + * - OC\Preview\Movie + * - OC\Preview\MSOfficeDoc + * - OC\Preview\MSOffice2003 + * - OC\Preview\MSOffice2007 + * - OC\Preview\OpenDocument + * - OC\Preview\PDF + * - OC\Preview\Photoshop + * - OC\Preview\Postscript + * - OC\Preview\StarOffice + * - OC\Preview\SVG + * - OC\Preview\TIFF + * + * @return array + */ + protected function getEnabledDefaultProvider() { + if ($this->defaultProviders !== null) { + return $this->defaultProviders; + } + + $imageProviders = [ + Preview\PNG::class, + Preview\JPEG::class, + Preview\GIF::class, + Preview\BMP::class, + Preview\HEIC::class, + Preview\XBitmap::class, + Preview\Krita::class, + ]; + + $this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([ + Preview\MarkDown::class, + Preview\MP3::class, + Preview\TXT::class, + Preview\OpenDocument::class, + ], $imageProviders)); + + if (in_array(Preview\Image::class, $this->defaultProviders)) { + $this->defaultProviders = array_merge($this->defaultProviders, $imageProviders); + } + $this->defaultProviders = array_unique($this->defaultProviders); + return $this->defaultProviders; + } + + /** + * Register the default providers (if enabled) + * + * @param string $class + * @param string $mimeType + */ + protected function registerCoreProvider($class, $mimeType, $options = []) { + if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { + $this->registerProvider($mimeType, function () use ($class, $options) { + return new $class($options); + }); + } + } + + /** + * Register the default providers (if enabled) + */ + protected function registerCoreProviders() { + if ($this->registeredCoreProviders) { + return; + } + $this->registeredCoreProviders = true; + + $this->registerCoreProvider(Preview\TXT::class, '/text\/plain/'); + $this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/'); + $this->registerCoreProvider(Preview\PNG::class, '/image\/png/'); + $this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/'); + $this->registerCoreProvider(Preview\GIF::class, '/image\/gif/'); + $this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/'); + $this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/'); + $this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/'); + $this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg/'); + $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/'); + + // SVG, Office and Bitmap require imagick + if (extension_loaded('imagick')) { + $checkImagick = new \Imagick(); + + $imagickProviders = [ + 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], + 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], + 'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class], + 'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class], + 'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class], + 'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class], + 'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class], + 'HEIC' => ['mimetype' => '/image\/hei(f|c)/', 'class' => Preview\HEIC::class], + ]; + + foreach ($imagickProviders as $queryFormat => $provider) { + $class = $provider['class']; + if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { + continue; + } + + if (count($checkImagick->queryFormats($queryFormat)) === 1) { + $this->registerCoreProvider($class, $provider['mimetype']); + } + } + + if (count($checkImagick->queryFormats('PDF')) === 1) { + if (\OC_Helper::is_function_enabled('shell_exec')) { + $officeFound = is_string($this->config->getSystemValue('preview_libreoffice_path', null)); + + if (!$officeFound) { + //let's see if there is libreoffice or openoffice on this machine + $whichLibreOffice = shell_exec('command -v libreoffice'); + $officeFound = !empty($whichLibreOffice); + if (!$officeFound) { + $whichOpenOffice = shell_exec('command -v openoffice'); + $officeFound = !empty($whichOpenOffice); + } + } + + if ($officeFound) { + $this->registerCoreProvider(Preview\MSOfficeDoc::class, '/application\/msword/'); + $this->registerCoreProvider(Preview\MSOffice2003::class, '/application\/vnd.ms-.*/'); + $this->registerCoreProvider(Preview\MSOffice2007::class, '/application\/vnd.openxmlformats-officedocument.*/'); + $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/'); + $this->registerCoreProvider(Preview\StarOffice::class, '/application\/vnd.sun.xml.*/'); + } + } + } + } + + // Video requires avconv or ffmpeg + if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) { + $avconvBinary = \OC_Helper::findBinaryPath('avconv'); + $ffmpegBinary = $avconvBinary ? null : \OC_Helper::findBinaryPath('ffmpeg'); + + if ($avconvBinary || $ffmpegBinary) { + // FIXME // a bit hacky but didn't want to use subclasses + \OC\Preview\Movie::$avconvBinary = $avconvBinary; + \OC\Preview\Movie::$ffmpegBinary = $ffmpegBinary; + + $this->registerCoreProvider(Preview\Movie::class, '/video\/.*/'); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/PreviewNotAvailableException.php b/docker/overlays/nextcloud/html/lib/private/PreviewNotAvailableException.php new file mode 100644 index 0000000..eb41118 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/PreviewNotAvailableException.php @@ -0,0 +1,27 @@ + + * + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC; + +class PreviewNotAvailableException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/RedisFactory.php b/docker/overlays/nextcloud/html/lib/private/RedisFactory.php new file mode 100644 index 0000000..52e69a1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/RedisFactory.php @@ -0,0 +1,118 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class RedisFactory { + /** @var \Redis */ + private $instance; + + /** @var SystemConfig */ + private $config; + + /** + * RedisFactory constructor. + * + * @param SystemConfig $config + */ + public function __construct(SystemConfig $config) { + $this->config = $config; + } + + private function create() { + if ($config = $this->config->getValue('redis.cluster', [])) { + if (!class_exists('RedisCluster')) { + throw new \Exception('Redis Cluster support is not available'); + } + // cluster config + if (isset($config['timeout'])) { + $timeout = $config['timeout']; + } else { + $timeout = null; + } + if (isset($config['read_timeout'])) { + $readTimeout = $config['read_timeout']; + } else { + $readTimeout = null; + } + if (isset($config['password']) && $config['password'] !== '') { + $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $config['password']); + } else { + $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout); + } + + if (isset($config['failover_mode'])) { + $this->instance->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, $config['failover_mode']); + } + } else { + $this->instance = new \Redis(); + $config = $this->config->getValue('redis', []); + if (isset($config['host'])) { + $host = $config['host']; + } else { + $host = '127.0.0.1'; + } + if (isset($config['port'])) { + $port = $config['port']; + } elseif ($host[0] !== '/') { + $port = 6379; + } else { + $port = null; + } + if (isset($config['timeout'])) { + $timeout = $config['timeout']; + } else { + $timeout = 0.0; // unlimited + } + + $this->instance->connect($host, $port, $timeout); + if (isset($config['password']) && $config['password'] !== '') { + $this->instance->auth($config['password']); + } + + if (isset($config['dbindex'])) { + $this->instance->select($config['dbindex']); + } + } + } + + public function getInstance() { + if (!$this->isAvailable()) { + throw new \Exception('Redis support is not available'); + } + if (!$this->instance instanceof \Redis) { + $this->create(); + } + + return $this->instance; + } + + public function isAvailable() { + return extension_loaded('redis') + && version_compare(phpversion('redis'), '2.2.5', '>='); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiBase.php b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiBase.php new file mode 100644 index 0000000..1da2d00 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiBase.php @@ -0,0 +1,99 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote\Api; + +use OCP\Http\Client\IClientService; +use OCP\Remote\ICredentials; +use OCP\Remote\IInstance; + +class ApiBase { + /** @var IInstance */ + private $instance; + /** @var ICredentials */ + private $credentials; + /** @var IClientService */ + private $clientService; + + public function __construct(IInstance $instance, ICredentials $credentials, IClientService $clientService) { + $this->instance = $instance; + $this->credentials = $credentials; + $this->clientService = $clientService; + } + + protected function getHttpClient() { + return $this->clientService->newClient(); + } + + protected function addDefaultHeaders(array $headers) { + return array_merge([ + 'OCS-APIREQUEST' => 'true', + 'Accept' => 'application/json' + ], $headers); + } + + /** + * @param string $method + * @param string $url + * @param array $body + * @param array $query + * @param array $headers + * @return resource|string + * @throws \InvalidArgumentException + */ + protected function request($method, $url, array $body = [], array $query = [], array $headers = []) { + $fullUrl = trim($this->instance->getFullUrl(), '/') . '/' . $url; + $options = [ + 'query' => $query, + 'headers' => $this->addDefaultHeaders($headers), + 'auth' => [$this->credentials->getUsername(), $this->credentials->getPassword()] + ]; + if ($body) { + $options['body'] = $body; + } + + $client = $this->getHttpClient(); + + switch ($method) { + case 'get': + $response = $client->get($fullUrl, $options); + break; + case 'post': + $response = $client->post($fullUrl, $options); + break; + case 'put': + $response = $client->put($fullUrl, $options); + break; + case 'delete': + $response = $client->delete($fullUrl, $options); + break; + case 'options': + $response = $client->options($fullUrl, $options); + break; + default: + throw new \InvalidArgumentException('Invalid method ' . $method); + } + + return $response->getBody(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiCollection.php b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiCollection.php new file mode 100644 index 0000000..9ce4c7d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiCollection.php @@ -0,0 +1,52 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote\Api; + +use OCP\Http\Client\IClientService; +use OCP\Remote\Api\IApiCollection; +use OCP\Remote\ICredentials; +use OCP\Remote\IInstance; + +class ApiCollection implements IApiCollection { + /** @var IInstance */ + private $instance; + /** @var ICredentials */ + private $credentials; + /** @var IClientService */ + private $clientService; + + public function __construct(IInstance $instance, ICredentials $credentials, IClientService $clientService) { + $this->instance = $instance; + $this->credentials = $credentials; + $this->clientService = $clientService; + } + + public function getCapabilitiesApi() { + return new OCS($this->instance, $this->credentials, $this->clientService); + } + + public function getUserApi() { + return new OCS($this->instance, $this->credentials, $this->clientService); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiFactory.php b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiFactory.php new file mode 100644 index 0000000..81bc41f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Api/ApiFactory.php @@ -0,0 +1,42 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote\Api; + +use OCP\Http\Client\IClientService; +use OCP\Remote\Api\IApiFactory; +use OCP\Remote\ICredentials; +use OCP\Remote\IInstance; + +class ApiFactory implements IApiFactory { + /** @var IClientService */ + private $clientService; + + public function __construct(IClientService $clientService) { + $this->clientService = $clientService; + } + + public function getApiCollection(IInstance $instance, ICredentials $credentials) { + return new ApiCollection($instance, $credentials, $this->clientService); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Api/NotFoundException.php b/docker/overlays/nextcloud/html/lib/private/Remote/Api/NotFoundException.php new file mode 100644 index 0000000..fadf4a4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Api/NotFoundException.php @@ -0,0 +1,27 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote\Api; + +class NotFoundException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Api/OCS.php b/docker/overlays/nextcloud/html/lib/private/Remote/Api/OCS.php new file mode 100644 index 0000000..770dce3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Api/OCS.php @@ -0,0 +1,101 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote\Api; + +use GuzzleHttp\Exception\ClientException; +use OC\ForbiddenException; +use OC\Remote\User; +use OCP\API; +use OCP\Remote\Api\ICapabilitiesApi; +use OCP\Remote\Api\IUserApi; + +class OCS extends ApiBase implements ICapabilitiesApi, IUserApi { + /** + * @param string $method + * @param string $url + * @param array $body + * @param array $query + * @param array $headers + * @return array + * @throws ForbiddenException + * @throws NotFoundException + * @throws \Exception + */ + protected function request($method, $url, array $body = [], array $query = [], array $headers = []) { + try { + $response = json_decode(parent::request($method, 'ocs/v2.php/' . $url, $body, $query, $headers), true); + } catch (ClientException $e) { + if ($e->getResponse()->getStatusCode() === 404) { + throw new NotFoundException(); + } elseif ($e->getResponse()->getStatusCode() === 403 || $e->getResponse()->getStatusCode() === 401) { + throw new ForbiddenException(); + } else { + throw $e; + } + } + if (!isset($response['ocs']) || !isset($response['ocs']['meta'])) { + throw new \Exception('Invalid ocs response'); + } + if ($response['ocs']['meta']['statuscode'] === API::RESPOND_UNAUTHORISED) { + throw new ForbiddenException(); + } + if ($response['ocs']['meta']['statuscode'] === API::RESPOND_NOT_FOUND) { + throw new NotFoundException(); + } + if ($response['ocs']['meta']['status'] !== 'ok') { + throw new \Exception('Unknown ocs error ' . $response['ocs']['meta']['message']); + } + + return $response['ocs']['data']; + } + + /** + * @param array $data + * @param string $type + * @param string[] $keys + * @throws \Exception + */ + private function checkResponseArray(array $data, $type, array $keys) { + foreach ($keys as $key) { + if (!array_key_exists($key, $data)) { + throw new \Exception('Invalid ' . $type . ' response, expected field ' . $key . ' not found'); + } + } + } + + public function getUser($userId) { + $result = $this->request('get', 'cloud/users/' . $userId); + $this->checkResponseArray($result, 'user', User::EXPECTED_KEYS); + return new User($result); + } + + /** + * @return array The capabilities in the form of [$appId => [$capability => $value]] + */ + public function getCapabilities() { + $result = $this->request('get', 'cloud/capabilities'); + return $result['capabilities']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Credentials.php b/docker/overlays/nextcloud/html/lib/private/Remote/Credentials.php new file mode 100644 index 0000000..09f5e1c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Credentials.php @@ -0,0 +1,56 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote; + +use OCP\Remote\ICredentials; + +class Credentials implements ICredentials { + /** @var string */ + private $user; + /** @var string */ + private $password; + + /** + * @param string $user + * @param string $password + */ + public function __construct($user, $password) { + $this->user = $user; + $this->password = $password; + } + + /** + * @return string + */ + public function getUsername() { + return $this->user; + } + + /** + * @return string + */ + public function getPassword() { + return $this->password; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/Instance.php b/docker/overlays/nextcloud/html/lib/private/Remote/Instance.php new file mode 100644 index 0000000..3b04e17 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/Instance.php @@ -0,0 +1,149 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote; + +use OC\Remote\Api\NotFoundException; +use OCP\Http\Client\IClientService; +use OCP\ICache; +use OCP\Remote\IInstance; + +/** + * Provides some basic info about a remote Nextcloud instance + */ +class Instance implements IInstance { + /** @var string */ + private $url; + + /** @var ICache */ + private $cache; + + /** @var IClientService */ + private $clientService; + + /** @var array|null */ + private $status; + + /** + * @param string $url + * @param ICache $cache + * @param IClientService $clientService + */ + public function __construct($url, ICache $cache, IClientService $clientService) { + $url = str_replace('https://', '', $url); + $this->url = str_replace('http://', '', $url); + $this->cache = $cache; + $this->clientService = $clientService; + } + + /** + * @return string The url of the remote server without protocol + */ + public function getUrl() { + return $this->url; + } + + /** + * @return string The of of the remote server with protocol + */ + public function getFullUrl() { + return $this->getProtocol() . '://' . $this->getUrl(); + } + + /** + * @return string The full version string in '13.1.2.3' format + */ + public function getVersion() { + $status = $this->getStatus(); + return $status['version']; + } + + /** + * @return string 'http' or 'https' + */ + public function getProtocol() { + $status = $this->getStatus(); + return $status['protocol']; + } + + /** + * Check that the remote server is installed and not in maintenance mode + * + * @return bool + */ + public function isActive() { + $status = $this->getStatus(); + return $status['installed'] && !$status['maintenance']; + } + + /** + * @return array + * @throws NotFoundException + * @throws \Exception + */ + private function getStatus() { + if ($this->status) { + return $this->status; + } + $key = 'remote/' . $this->url . '/status'; + $httpsKey = 'remote/' . $this->url . '/https'; + $status = $this->cache->get($key); + if (!$status) { + $response = $this->downloadStatus('https://' . $this->getUrl() . '/status.php'); + $protocol = 'https'; + if (!$response) { + if ($status = $this->cache->get($httpsKey)) { + throw new \Exception('refusing to connect to remote instance(' . $this->url . ') over http that was previously accessible over https'); + } + $response = $this->downloadStatus('http://' . $this->getUrl() . '/status.php'); + $protocol = 'http'; + } else { + $this->cache->set($httpsKey, true, 60 * 60 * 24 * 365); + } + $status = json_decode($response, true); + if ($status) { + $status['protocol'] = $protocol; + } + if ($status) { + $this->cache->set($key, $status, 5 * 60); + $this->status = $status; + } else { + throw new NotFoundException('Remote server not found at address ' . $this->url); + } + } + return $status; + } + + /** + * @param string $url + * @return bool|string + */ + private function downloadStatus($url) { + try { + $request = $this->clientService->newClient()->get($url); + return $request->getBody(); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/InstanceFactory.php b/docker/overlays/nextcloud/html/lib/private/Remote/InstanceFactory.php new file mode 100644 index 0000000..a4445a8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/InstanceFactory.php @@ -0,0 +1,44 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote; + +use OCP\Http\Client\IClientService; +use OCP\ICache; +use OCP\Remote\IInstanceFactory; + +class InstanceFactory implements IInstanceFactory { + /** @var ICache */ + private $cache; + /** @var IClientService */ + private $clientService; + + public function __construct(ICache $cache, IClientService $clientService) { + $this->cache = $cache; + $this->clientService = $clientService; + } + + public function getInstance($url) { + return new Instance($url, $this->cache, $this->clientService); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Remote/User.php b/docker/overlays/nextcloud/html/lib/private/Remote/User.php new file mode 100644 index 0000000..fbf30b3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Remote/User.php @@ -0,0 +1,140 @@ + + * + * @author Christoph Wurst + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Remote; + +use OCP\Remote\IUser; + +class User implements IUser { + public const EXPECTED_KEYS = [ + 'id', + 'email', + 'displayname', + 'phone', + 'address', + 'website', + 'groups', + 'language', + 'quota' + ]; + + /** @var array */ + private $data; + + public function __construct(array $data) { + $this->data = $data; + } + + + /** + * @return string + */ + public function getUserId() { + return $this->data['id']; + } + + /** + * @return string + */ + public function getEmail() { + return $this->data['email']; + } + + /** + * @return string + */ + public function getDisplayName() { + return $this->data['displayname']; + } + + /** + * @return string + */ + public function getPhone() { + return $this->data['phone']; + } + + /** + * @return string + */ + public function getAddress() { + return $this->data['address']; + } + + /** + * @return string + */ + public function getWebsite() { + return $this->data['website']; + } + + /** + * @return string + */ + public function getTwitter() { + return isset($this->data['twitter']) ? $this->data['twitter'] : ''; + } + + /** + * @return string[] + */ + public function getGroups() { + return $this->data['groups']; + } + + /** + * @return string + */ + public function getLanguage() { + return $this->data['language']; + } + + /** + * @return int + */ + public function getUsedSpace() { + return $this->data['quota']['used']; + } + + /** + * @return int + */ + public function getFreeSpace() { + return $this->data['quota']['free']; + } + + /** + * @return int + */ + public function getTotalSpace() { + return $this->data['quota']['total']; + } + + /** + * @return int + */ + public function getQuota() { + return $this->data['quota']['quota']; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair.php b/docker/overlays/nextcloud/html/lib/private/Repair.php new file mode 100644 index 0000000..bba797a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair.php @@ -0,0 +1,248 @@ + + * @author Daniel Kesselberg + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Weimann + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\Avatar\AvatarManager; +use OC\Repair\AddCleanupUpdaterBackupsJob; +use OC\Repair\CleanTags; +use OC\Repair\ClearFrontendCaches; +use OC\Repair\ClearGeneratedAvatarCache; +use OC\Repair\Collation; +use OC\Repair\MoveUpdaterStepFile; +use OC\Repair\NC11\FixMountStorages; +use OC\Repair\NC13\AddLogRotateJob; +use OC\Repair\NC14\AddPreviewBackgroundCleanupJob; +use OC\Repair\NC16\AddClenupLoginFlowV2BackgroundJob; +use OC\Repair\NC16\CleanupCardDAVPhotoCache; +use OC\Repair\NC16\ClearCollectionsAccessCache; +use OC\Repair\NC18\ResetGeneratedAvatarFlag; +use OC\Repair\NC20\EncryptionLegacyCipher; +use OC\Repair\NC20\EncryptionMigration; +use OC\Repair\NC20\ShippedDashboardEnable; +use OC\Repair\OldGroupMembershipShares; +use OC\Repair\Owncloud\DropAccountTermsTable; +use OC\Repair\Owncloud\SaveAccountsTableData; +use OC\Repair\RemoveLinkShares; +use OC\Repair\RepairInvalidShares; +use OC\Repair\RepairMimeTypes; +use OC\Repair\SqliteAutoincrement; +use OC\Template\JSCombiner; +use OC\Template\SCSSCacher; +use OCP\AppFramework\QueryException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Collaboration\Resources\IManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Repair implements IOutput { + + /** @var IRepairStep[] */ + private $repairSteps; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + /** @var string */ + private $currentStep; + + /** + * Creates a new repair step runner + * + * @param IRepairStep[] $repairSteps array of RepairStep instances + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(array $repairSteps, EventDispatcherInterface $dispatcher) { + $this->repairSteps = $repairSteps; + $this->dispatcher = $dispatcher; + } + + /** + * Run a series of repair steps for common problems + */ + public function run() { + if (count($this->repairSteps) === 0) { + $this->emit('\OC\Repair', 'info', ['No repair steps available']); + + return; + } + // run each repair step + foreach ($this->repairSteps as $step) { + $this->currentStep = $step->getName(); + $this->emit('\OC\Repair', 'step', [$this->currentStep]); + $step->run($this); + } + } + + /** + * Add repair step + * + * @param IRepairStep|string $repairStep repair step + * @throws \Exception + */ + public function addStep($repairStep) { + if (is_string($repairStep)) { + try { + $s = \OC::$server->query($repairStep); + } catch (QueryException $e) { + if (class_exists($repairStep)) { + $s = new $repairStep(); + } else { + throw new \Exception("Repair step '$repairStep' is unknown"); + } + } + + if ($s instanceof IRepairStep) { + $this->repairSteps[] = $s; + } else { + throw new \Exception("Repair step '$repairStep' is not of type \\OCP\\Migration\\IRepairStep"); + } + } else { + $this->repairSteps[] = $repairStep; + } + } + + /** + * Returns the default repair steps to be run on the + * command line or after an upgrade. + * + * @return IRepairStep[] + */ + public static function getRepairSteps() { + return [ + new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false), + new RepairMimeTypes(\OC::$server->getConfig()), + new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()), + new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), + new MoveUpdaterStepFile(\OC::$server->getConfig()), + new FixMountStorages(\OC::$server->getDatabaseConnection()), + new AddLogRotateJob(\OC::$server->getJobList()), + new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)), + new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)), + new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()), + new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()), + new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->getLogger()), + new AddClenupLoginFlowV2BackgroundJob(\OC::$server->getJobList()), + new RemoveLinkShares(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getGroupManager(), \OC::$server->getNotificationManager(), \OC::$server->query(ITimeFactory::class)), + new ClearCollectionsAccessCache(\OC::$server->getConfig(), \OC::$server->query(IManager::class)), + \OC::$server->query(ResetGeneratedAvatarFlag::class), + \OC::$server->query(EncryptionLegacyCipher::class), + \OC::$server->query(EncryptionMigration::class), + \OC::$server->get(ShippedDashboardEnable::class), + ]; + } + + /** + * Returns expensive repair steps to be run on the + * command line with a special option. + * + * @return IRepairStep[] + */ + public static function getExpensiveRepairSteps() { + return [ + new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()) + ]; + } + + /** + * Returns the repair steps to be run before an + * upgrade. + * + * @return IRepairStep[] + */ + public static function getBeforeUpgradeRepairSteps() { + $connection = \OC::$server->getDatabaseConnection(); + $config = \OC::$server->getConfig(); + $steps = [ + new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true), + new SqliteAutoincrement($connection), + new SaveAccountsTableData($connection, $config), + new DropAccountTermsTable($connection) + ]; + + return $steps; + } + + /** + * @param string $scope + * @param string $method + * @param array $arguments + */ + public function emit($scope, $method, array $arguments = []) { + if (!is_null($this->dispatcher)) { + $this->dispatcher->dispatch("$scope::$method", + new GenericEvent("$scope::$method", $arguments)); + } + } + + public function info($string) { + // for now just emit as we did in the past + $this->emit('\OC\Repair', 'info', [$string]); + } + + /** + * @param string $message + */ + public function warning($message) { + // for now just emit as we did in the past + $this->emit('\OC\Repair', 'warning', [$message]); + } + + /** + * @param int $max + */ + public function startProgress($max = 0) { + // for now just emit as we did in the past + $this->emit('\OC\Repair', 'startProgress', [$max, $this->currentStep]); + } + + /** + * @param int $step + * @param string $description + */ + public function advance($step = 1, $description = '') { + // for now just emit as we did in the past + $this->emit('\OC\Repair', 'advance', [$step, $description]); + } + + /** + * @param int $max + */ + public function finishProgress() { + // for now just emit as we did in the past + $this->emit('\OC\Repair', 'finishProgress', []); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/AddCleanupUpdaterBackupsJob.php b/docker/overlays/nextcloud/html/lib/private/Repair/AddCleanupUpdaterBackupsJob.php new file mode 100644 index 0000000..44c1942 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/AddCleanupUpdaterBackupsJob.php @@ -0,0 +1,47 @@ + + * + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair; + +use OC\Core\BackgroundJobs\BackgroundCleanupUpdaterBackupsJob; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddCleanupUpdaterBackupsJob implements IRepairStep { + + /** @var IJobList */ + protected $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName() { + return 'Queue a one-time job to cleanup old backups of the updater'; + } + + public function run(IOutput $output) { + $this->jobList->add(BackgroundCleanupUpdaterBackupsJob::class); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/CleanTags.php b/docker/overlays/nextcloud/html/lib/private/Repair/CleanTags.php new file mode 100644 index 0000000..f414ef0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/CleanTags.php @@ -0,0 +1,211 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class RepairConfig + * + * @package OC\Repair + */ +class CleanTags implements IRepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** @var IUserManager */ + protected $userManager; + + protected $deletedTags = 0; + + /** + * @param IDBConnection $connection + * @param IUserManager $userManager + */ + public function __construct(IDBConnection $connection, IUserManager $userManager) { + $this->connection = $connection; + $this->userManager = $userManager; + } + + /** + * @return string + */ + public function getName() { + return 'Clean tags and favorites'; + } + + /** + * Updates the configuration after running an update + */ + public function run(IOutput $output) { + $this->deleteOrphanTags($output); + $this->deleteOrphanFileEntries($output); + $this->deleteOrphanTagEntries($output); + $this->deleteOrphanCategoryEntries($output); + } + + /** + * Delete tags for deleted users + */ + protected function deleteOrphanTags(IOutput $output) { + $offset = 0; + while ($this->checkTags($offset)) { + $offset += 50; + } + + $output->info(sprintf('%d tags of deleted users have been removed.', $this->deletedTags)); + } + + protected function checkTags($offset) { + $query = $this->connection->getQueryBuilder(); + $query->select('uid') + ->from('vcategory') + ->groupBy('uid') + ->orderBy('uid') + ->setMaxResults(50) + ->setFirstResult($offset); + $result = $query->execute(); + + $users = []; + $hadResults = false; + while ($row = $result->fetch()) { + $hadResults = true; + if (!$this->userManager->userExists($row['uid'])) { + $users[] = $row['uid']; + } + } + $result->closeCursor(); + + if (!$hadResults) { + // No more tags, stop looping + return false; + } + + if (!empty($users)) { + $query = $this->connection->getQueryBuilder(); + $query->delete('vcategory') + ->where($query->expr()->in('uid', $query->createNamedParameter($users, IQueryBuilder::PARAM_STR_ARRAY))); + $this->deletedTags += $query->execute(); + } + return true; + } + + /** + * Delete tag entries for deleted files + */ + protected function deleteOrphanFileEntries(IOutput $output) { + $this->deleteOrphanEntries( + $output, + '%d tags for delete files have been removed.', + 'vcategory_to_object', 'objid', + 'filecache', 'fileid', 'path_hash' + ); + } + + /** + * Delete tag entries for deleted tags + */ + protected function deleteOrphanTagEntries(IOutput $output) { + $this->deleteOrphanEntries( + $output, + '%d tag entries for deleted tags have been removed.', + 'vcategory_to_object', 'categoryid', + 'vcategory', 'id', 'uid' + ); + } + + /** + * Delete tags that have no entries + */ + protected function deleteOrphanCategoryEntries(IOutput $output) { + $this->deleteOrphanEntries( + $output, + '%d tags with no entries have been removed.', + 'vcategory', 'id', + 'vcategory_to_object', 'categoryid', 'type' + ); + } + + /** + * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable + * + * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks + * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable + * is being deleted. + * + * @param string $repairInfo + * @param string $deleteTable + * @param string $deleteId + * @param string $sourceTable + * @param string $sourceId + * @param string $sourceNullColumn If this column is null in the source table, + * the entry is deleted in the $deleteTable + */ + protected function deleteOrphanEntries(IOutput $output, $repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) { + $qb = $this->connection->getQueryBuilder(); + + $qb->select('d.' . $deleteId) + ->from($deleteTable, 'd') + ->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, 's.' . $sourceId)) + ->where( + $qb->expr()->eq('d.type', $qb->expr()->literal('files')) + ) + ->andWhere( + $qb->expr()->isNull('s.' . $sourceNullColumn) + ); + $result = $qb->execute(); + + $orphanItems = []; + while ($row = $result->fetch()) { + $orphanItems[] = (int) $row[$deleteId]; + } + + if (!empty($orphanItems)) { + $orphanItemsBatch = array_chunk($orphanItems, 200); + foreach ($orphanItemsBatch as $items) { + $qb->delete($deleteTable) + ->where( + $qb->expr()->eq('type', $qb->expr()->literal('files')) + ) + ->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids'))); + $qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY); + $qb->execute(); + } + } + + if ($repairInfo) { + $output->info(sprintf($repairInfo, count($orphanItems))); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/ClearFrontendCaches.php b/docker/overlays/nextcloud/html/lib/private/Repair/ClearFrontendCaches.php new file mode 100644 index 0000000..4f48d96 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/ClearFrontendCaches.php @@ -0,0 +1,72 @@ + + * + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair; + +use OC\Template\JSCombiner; +use OC\Template\SCSSCacher; +use OCP\ICacheFactory; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ClearFrontendCaches implements IRepairStep { + + /** @var ICacheFactory */ + protected $cacheFactory; + + /** @var SCSSCacher */ + protected $scssCacher; + + /** @var JSCombiner */ + protected $jsCombiner; + + public function __construct(ICacheFactory $cacheFactory, + SCSSCacher $SCSSCacher, + JSCombiner $JSCombiner) { + $this->cacheFactory = $cacheFactory; + $this->scssCacher = $SCSSCacher; + $this->jsCombiner = $JSCombiner; + } + + public function getName() { + return 'Clear frontend caches'; + } + + public function run(IOutput $output) { + try { + $c = $this->cacheFactory->createDistributed('imagePath'); + $c->clear(); + $output->info('Image cache cleared'); + + $this->scssCacher->resetCache(); + $output->info('SCSS cache cleared'); + + $this->jsCombiner->resetCache(); + $output->info('JS cache cleared'); + } catch (\Exception $e) { + $output->warning('Unable to clear the frontend cache'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/ClearGeneratedAvatarCache.php b/docker/overlays/nextcloud/html/lib/private/Repair/ClearGeneratedAvatarCache.php new file mode 100644 index 0000000..44a390d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/ClearGeneratedAvatarCache.php @@ -0,0 +1,71 @@ + + * + * @author John Molakvoæ (skjnldsv) + * @author Michael Weimann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair; + +use OC\Avatar\AvatarManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ClearGeneratedAvatarCache implements IRepairStep { + + /** @var AvatarManager */ + protected $avatarManager; + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config, AvatarManager $avatarManager) { + $this->config = $config; + $this->avatarManager = $avatarManager; + } + + public function getName() { + return 'Clear every generated avatar on major updates'; + } + + /** + * Check if this repair step should run + * + * @return boolean + */ + private function shouldRun() { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + + // was added to 15.0.0.4 + return version_compare($versionFromBeforeUpdate, '15.0.0.4', '<='); + } + + public function run(IOutput $output) { + if ($this->shouldRun()) { + try { + $this->avatarManager->clearCachedAvatars(); + $output->info('Avatar cache cleared'); + } catch (\Exception $e) { + $output->warning('Unable to clear the avatar cache'); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/Collation.php b/docker/overlays/nextcloud/html/lib/private/Repair/Collation.php new file mode 100644 index 0000000..fb0e019 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/Collation.php @@ -0,0 +1,154 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Robin Müller + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Platforms\MySqlPlatform; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class Collation implements IRepairStep { + /** @var IConfig */ + protected $config; + + /** @var ILogger */ + protected $logger; + + /** @var IDBConnection */ + protected $connection; + + /** @var bool */ + protected $ignoreFailures; + + /** + * @param IConfig $config + * @param ILogger $logger + * @param IDBConnection $connection + * @param bool $ignoreFailures + */ + public function __construct(IConfig $config, ILogger $logger, IDBConnection $connection, $ignoreFailures) { + $this->connection = $connection; + $this->config = $config; + $this->logger = $logger; + $this->ignoreFailures = $ignoreFailures; + } + + public function getName() { + return 'Repair MySQL collation'; + } + + /** + * Fix mime types + */ + public function run(IOutput $output) { + if (!$this->connection->getDatabasePlatform() instanceof MySqlPlatform) { + $output->info('Not a mysql database -> nothing to do'); + return; + } + + $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; + + $tables = $this->getAllNonUTF8BinTables($this->connection); + foreach ($tables as $table) { + $output->info("Change row format for $table ..."); + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` ROW_FORMAT = DYNAMIC;'); + try { + $query->execute(); + } catch (DriverException $e) { + // Just log this + $this->logger->logException($e); + if (!$this->ignoreFailures) { + throw $e; + } + } + + $output->info("Change collation for $table ..."); + if ($characterSet === 'utf8mb4') { + // need to set row compression first + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` ROW_FORMAT=COMPRESSED;'); + $query->execute(); + } + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $characterSet . '_bin;'); + try { + $query->execute(); + } catch (DriverException $e) { + // Just log this + $this->logger->logException($e); + if (!$this->ignoreFailures) { + throw $e; + } + } + } + if (empty($tables)) { + $output->info('All tables already have the correct collation -> nothing to do'); + } + } + + /** + * @param IDBConnection $connection + * @return string[] + */ + protected function getAllNonUTF8BinTables(IDBConnection $connection) { + $dbName = $this->config->getSystemValue("dbname"); + $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; + + // fetch tables by columns + $statement = $connection->executeQuery( + "SELECT DISTINCT(TABLE_NAME) AS `table`" . + " FROM INFORMATION_SCHEMA . COLUMNS" . + " WHERE TABLE_SCHEMA = ?" . + " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" . + " AND TABLE_NAME LIKE '*PREFIX*%'", + [$dbName] + ); + $rows = $statement->fetchAll(); + $result = []; + foreach ($rows as $row) { + $result[$row['table']] = true; + } + + // fetch tables by collation + $statement = $connection->executeQuery( + "SELECT DISTINCT(TABLE_NAME) AS `table`" . + " FROM INFORMATION_SCHEMA . TABLES" . + " WHERE TABLE_SCHEMA = ?" . + " AND TABLE_COLLATION <> '" . $characterSet . "_bin'" . + " AND TABLE_NAME LIKE '*PREFIX*%'", + [$dbName] + ); + $rows = $statement->fetchAll(); + foreach ($rows as $row) { + $result[$row['table']] = true; + } + + return array_keys($result); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/MoveUpdaterStepFile.php b/docker/overlays/nextcloud/html/lib/private/Repair/MoveUpdaterStepFile.php new file mode 100644 index 0000000..e912838 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/MoveUpdaterStepFile.php @@ -0,0 +1,79 @@ + + * + * @author Christoph Wurst + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair; + +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class MoveUpdaterStepFile implements IRepairStep { + + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct($config) { + $this->config = $config; + } + + public function getName() { + return 'Move .step file of updater to backup location'; + } + + public function run(IOutput $output) { + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data'); + $instanceId = $this->config->getSystemValue('instanceid', null); + + if (!is_string($instanceId) || empty($instanceId)) { + return; + } + + $updaterFolderPath = $dataDir . '/updater-' . $instanceId; + $stepFile = $updaterFolderPath . '/.step'; + if (file_exists($stepFile)) { + $output->info('.step file exists'); + + $previousStepFile = $updaterFolderPath . '/.step-previous-update'; + + // cleanup + if (file_exists($previousStepFile)) { + if (\OC_Helper::rmdirr($previousStepFile)) { + $output->info('.step-previous-update removed'); + } else { + $output->info('.step-previous-update can\'t be removed - abort move of .step file'); + return; + } + } + + // move step file + if (rename($stepFile, $previousStepFile)) { + $output->info('.step file moved to .step-previous-update'); + } else { + $output->warning('.step file can\'t be moved'); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC11/FixMountStorages.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC11/FixMountStorages.php new file mode 100644 index 0000000..199b504 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC11/FixMountStorages.php @@ -0,0 +1,80 @@ + + * + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC11; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class FixMountStorages implements IRepairStep { + + /** @var IDBConnection */ + private $db; + + /** + * @param IDBConnection $db + */ + public function __construct(IDBConnection $db) { + $this->db = $db; + } + + /** + * @return string + */ + public function getName() { + return 'Fix potential broken mount points'; + } + + public function run(IOutput $output) { + $query = $this->db->getQueryBuilder(); + $query->select('m.id', 'f.storage') + ->from('mounts', 'm') + ->leftJoin('m', 'filecache', 'f', $query->expr()->eq('m.root_id', 'f.fileid')) + ->where($query->expr()->neq('m.storage_id', 'f.storage')); + + $update = $this->db->getQueryBuilder(); + $update->update('mounts') + ->set('storage_id', $update->createParameter('storage')) + ->where($query->expr()->eq('id', $update->createParameter('mount'))); + + $result = $query->execute(); + $entriesUpdated = 0; + while ($row = $result->fetch()) { + $update->setParameter('storage', $row['storage'], IQueryBuilder::PARAM_INT) + ->setParameter('mount', $row['id'], IQueryBuilder::PARAM_INT); + $update->execute(); + $entriesUpdated++; + } + $result->closeCursor(); + + if ($entriesUpdated > 0) { + $output->info($entriesUpdated . ' mounts updated'); + return; + } + + $output->info('No mounts updated'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC13/AddLogRotateJob.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC13/AddLogRotateJob.php new file mode 100644 index 0000000..7bd2908 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC13/AddLogRotateJob.php @@ -0,0 +1,47 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC13; + +use OC\Log\Rotate; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddLogRotateJob implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName() { + return 'Add log rotate job'; + } + + public function run(IOutput $output) { + $this->jobList->add(Rotate::class); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php new file mode 100644 index 0000000..f2958de --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php @@ -0,0 +1,50 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC14; + +use OC\Preview\BackgroundCleanupJob; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddPreviewBackgroundCleanupJob implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName(): string { + return 'Add preview background cleanup job'; + } + + public function run(IOutput $output) { + $this->jobList->add(BackgroundCleanupJob::class); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php new file mode 100644 index 0000000..34afd5d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php @@ -0,0 +1,50 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC16; + +use OC\Core\BackgroundJobs\CleanupLoginFlowV2; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddClenupLoginFlowV2BackgroundJob implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName(): string { + return 'Add background job to cleanup login flow v2 tokens'; + } + + public function run(IOutput $output) { + $this->jobList->add(CleanupLoginFlowV2::class); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php new file mode 100644 index 0000000..fb09097 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php @@ -0,0 +1,113 @@ + + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC16; + +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use RuntimeException; + +/** + * Class CleanupCardDAVPhotoCache + * + * This repair step removes "photo." files created by photocache + * + * Before https://github.com/nextcloud/server/pull/13843 a "photo." file could be created + * for unsupported image formats by photocache. Because a file is present but not jpg, png or gif no + * photo could be returned for this vcard. These invalid files are removed by this migration step. + */ +class CleanupCardDAVPhotoCache implements IRepairStep { + + /** @var IConfig */ + private $config; + + /** @var IAppData */ + private $appData; + + /** @var ILogger */ + private $logger; + + public function __construct(IConfig $config, IAppData $appData, ILogger $logger) { + $this->config = $config; + $this->appData = $appData; + $this->logger = $logger; + } + + public function getName(): string { + return 'Cleanup invalid photocache files for carddav'; + } + + private function repair(IOutput $output): void { + try { + $folders = $this->appData->getDirectoryListing(); + } catch (NotFoundException $e) { + return; + } catch (RuntimeException $e) { + $this->logger->logException($e, ['message' => 'Failed to fetch directory listing in CleanupCardDAVPhotoCache']); + return; + } + + $folders = array_filter($folders, function (ISimpleFolder $folder) { + return $folder->fileExists('photo.'); + }); + + if (empty($folders)) { + return; + } + + $output->info('Delete ' . count($folders) . ' "photo." files'); + + foreach ($folders as $folder) { + try { + /** @var ISimpleFolder $folder */ + $folder->getFile('photo.')->delete(); + } catch (\Exception $e) { + $this->logger->logException($e); + $output->warning('Could not delete file "dav-photocache/' . $folder->getName() . '/photo."'); + } + } + } + + private function shouldRun(): bool { + return version_compare( + $this->config->getSystemValue('version', '0.0.0.0'), + '16.0.0.0', + '<=' + ); + } + + public function run(IOutput $output): void { + if ($this->shouldRun()) { + $this->repair($output); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC16/ClearCollectionsAccessCache.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/ClearCollectionsAccessCache.php new file mode 100644 index 0000000..6d13a8f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC16/ClearCollectionsAccessCache.php @@ -0,0 +1,62 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC16; + +use OC\Collaboration\Resources\Manager; +use OCP\Collaboration\Resources\IManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ClearCollectionsAccessCache implements IRepairStep { + + /** @var IConfig */ + private $config; + + /** @var IManager|Manager */ + private $manager; + + public function __construct(IConfig $config, IManager $manager) { + $this->config = $config; + $this->manager = $manager; + } + + public function getName(): string { + return 'Clear access cache of projects'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '17.0.0.3', '<='); + } + + public function run(IOutput $output): void { + if ($this->shouldRun()) { + $this->manager->invalidateAccessCacheForAllCollections(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php new file mode 100644 index 0000000..1a86b87 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php @@ -0,0 +1,64 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC18; + +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ResetGeneratedAvatarFlag implements IRepairStep { + + /** @var IConfig */ + private $config; + /** @var IDBConnection */ + private $connection; + + public function __construct(IConfig $config, + IDBConnection $connection) { + $this->config = $config; + $this->connection = $connection; + } + + public function getName(): string { + return 'Reset generated avatar flag'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '18.0.0.5', '<='); + } + + public function run(IOutput $output): void { + if ($this->shouldRun()) { + $query = $this->connection->getQueryBuilder(); + $query->delete('preferences') + ->where($query->expr()->eq('appid', $query->createNamedParameter('avatar'))) + ->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('generated'))); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionLegacyCipher.php new file mode 100644 index 0000000..7418a66 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionLegacyCipher.php @@ -0,0 +1,63 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC20; + +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class EncryptionLegacyCipher implements IRepairStep { + + /** @var IConfig */ + private $config; + /** @var IManager */ + private $manager; + + public function __construct(IConfig $config, + IManager $manager) { + $this->config = $config; + $this->manager = $manager; + } + + public function getName(): string { + return 'Keep legacy encryption enabled'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '20.0.0.0', '<='); + } + + public function run(IOutput $output): void { + if ($this->manager->isEnabled()) { + if ($this->config->getSystemValue('encryption.legacy_format_support', '') === '') { + $this->config->setSystemValue('encryption.legacy_format_support', true); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionMigration.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionMigration.php new file mode 100644 index 0000000..41681a3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/EncryptionMigration.php @@ -0,0 +1,63 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC20; + +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class EncryptionMigration implements IRepairStep { + + /** @var IConfig */ + private $config; + /** @var IManager */ + private $manager; + + public function __construct(IConfig $config, + IManager $manager) { + $this->config = $config; + $this->manager = $manager; + } + + public function getName(): string { + return 'Check encryption key format'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '20.0.0.1', '<='); + } + + public function run(IOutput $output): void { + if ($this->manager->isEnabled()) { + if ($this->config->getSystemValue('encryption.key_storage_migrated', '') === '') { + $this->config->setSystemValue('encryption.key_storage_migrated', false); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/NC20/ShippedDashboardEnable.php b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/ShippedDashboardEnable.php new file mode 100644 index 0000000..b890037 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/NC20/ShippedDashboardEnable.php @@ -0,0 +1,53 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + + +namespace OC\Repair\NC20; + +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ShippedDashboardEnable implements IRepairStep { + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + public function getName() { + return 'Remove old dashboard app config data'; + } + + public function run(IOutput $output) { + $version = $this->config->getAppValue('dashboard', 'version', '7.0.0'); + if (version_compare($version, '7.0.0', '<')) { + $this->config->deleteAppValues('dashboard'); + $output->info('Removed old dashboard app config'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/OldGroupMembershipShares.php b/docker/overlays/nextcloud/html/lib/private/Repair/OldGroupMembershipShares.php new file mode 100644 index 0000000..3825418 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/OldGroupMembershipShares.php @@ -0,0 +1,119 @@ + + * @author Lukas Reschke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OCP\Share\IShare; + +class OldGroupMembershipShares implements IRepairStep { + + /** @var \OCP\IDBConnection */ + protected $connection; + + /** @var \OCP\IGroupManager */ + protected $groupManager; + + /** + * @var array [gid => [uid => (bool)]] + */ + protected $memberships; + + /** + * @param IDBConnection $connection + * @param IGroupManager $groupManager + */ + public function __construct(IDBConnection $connection, IGroupManager $groupManager) { + $this->connection = $connection; + $this->groupManager = $groupManager; + } + + /** + * Returns the step's name + * + * @return string + */ + public function getName() { + return 'Remove shares of old group memberships'; + } + + /** + * Run repair step. + * Must throw exception on error. + * + * @throws \Exception in case of failure + */ + public function run(IOutput $output) { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('s1.id')->selectAlias('s1.share_with', 'user')->selectAlias('s2.share_with', 'group') + ->from('share', 's1') + ->where($query->expr()->isNotNull('s1.parent')) + // \OC\Share\Constant::$shareTypeGroupUserUnique === 2 + ->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2))) + ->andWhere($query->expr()->isNotNull('s2.id')) + ->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(IShare::TYPE_GROUP))) + ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id')); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($query->expr()->eq('id', $deleteQuery->createParameter('share'))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + if (!$this->isMember($row['group'], $row['user'])) { + $deletedEntries += $deleteQuery->setParameter('share', (int) $row['id']) + ->execute(); + } + } + $result->closeCursor(); + + if ($deletedEntries) { + $output->info('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore'); + } + } + + /** + * @param string $gid + * @param string $uid + * @return bool + */ + protected function isMember($gid, $uid) { + if (isset($this->memberships[$gid][$uid])) { + return $this->memberships[$gid][$uid]; + } + + $isMember = $this->groupManager->isInGroup($uid, $gid); + if (!isset($this->memberships[$gid])) { + $this->memberships[$gid] = []; + } + $this->memberships[$gid][$uid] = $isMember; + + return $isMember; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/DropAccountTermsTable.php b/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/DropAccountTermsTable.php new file mode 100644 index 0000000..c6e8a9d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/DropAccountTermsTable.php @@ -0,0 +1,59 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\Owncloud; + +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class DropAccountTermsTable implements IRepairStep { + + /** @var IDBConnection */ + protected $db; + + /** + * @param IDBConnection $db + */ + public function __construct(IDBConnection $db) { + $this->db = $db; + } + + /** + * @return string + */ + public function getName() { + return 'Drop account terms table when migrating from ownCloud'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + if (!$this->db->tableExists('account_terms')) { + return; + } + + $this->db->dropTable('account_terms'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/SaveAccountsTableData.php b/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/SaveAccountsTableData.php new file mode 100644 index 0000000..6ca4693 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/Owncloud/SaveAccountsTableData.php @@ -0,0 +1,190 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\Owncloud; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OCP\PreConditionNotMetException; + +/** + * Copies the email address from the accounts table to the preference table, + * before the data structure is changed and the information is gone + */ +class SaveAccountsTableData implements IRepairStep { + public const BATCH_SIZE = 75; + + /** @var IDBConnection */ + protected $db; + + /** @var IConfig */ + protected $config; + + protected $hasForeignKeyOnPersistentLocks = false; + + /** + * @param IDBConnection $db + * @param IConfig $config + */ + public function __construct(IDBConnection $db, IConfig $config) { + $this->db = $db; + $this->config = $config; + } + + /** + * @return string + */ + public function getName() { + return 'Copy data from accounts table when migrating from ownCloud'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + if (!$this->shouldRun()) { + return; + } + + $offset = 0; + $numUsers = $this->runStep($offset); + + while ($numUsers === self::BATCH_SIZE) { + $offset += $numUsers; + $numUsers = $this->runStep($offset); + } + + // Remove the table + if ($this->hasForeignKeyOnPersistentLocks) { + $this->db->dropTable('persistent_locks'); + } + $this->db->dropTable('accounts'); + } + + /** + * @return bool + */ + protected function shouldRun() { + $schema = $this->db->createSchema(); + $prefix = $this->config->getSystemValue('dbtableprefix', 'oc_'); + + $tableName = $prefix . 'accounts'; + if (!$schema->hasTable($tableName)) { + return false; + } + + $table = $schema->getTable($tableName); + if (!$table->hasColumn('user_id')) { + return false; + } + + if ($schema->hasTable($prefix . 'persistent_locks')) { + $locksTable = $schema->getTable($prefix . 'persistent_locks'); + $foreignKeys = $locksTable->getForeignKeys(); + foreach ($foreignKeys as $foreignKey) { + if ($tableName === $foreignKey->getForeignTableName()) { + $this->hasForeignKeyOnPersistentLocks = true; + } + } + } + + return true; + } + + /** + * @param int $offset + * @return int Number of copied users + */ + protected function runStep($offset) { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('accounts') + ->orderBy('id') + ->setMaxResults(self::BATCH_SIZE); + + if ($offset > 0) { + $query->setFirstResult($offset); + } + + $result = $query->execute(); + + $update = $this->db->getQueryBuilder(); + $update->update('users') + ->set('displayname', $update->createParameter('displayname')) + ->where($update->expr()->eq('uid', $update->createParameter('userid'))); + + $updatedUsers = 0; + while ($row = $result->fetch()) { + try { + $this->migrateUserInfo($update, $row); + } catch (PreConditionNotMetException $e) { + // Ignore and continue + } catch (\UnexpectedValueException $e) { + // Ignore and continue + } + $updatedUsers++; + } + $result->closeCursor(); + + return $updatedUsers; + } + + /** + * @param IQueryBuilder $update + * @param array $userdata + * @throws PreConditionNotMetException + * @throws \UnexpectedValueException + */ + protected function migrateUserInfo(IQueryBuilder $update, $userdata) { + $state = (int) $userdata['state']; + if ($state === 3) { + // Deleted user, ignore + return; + } + + if ($userdata['email'] !== null) { + $this->config->setUserValue($userdata['user_id'], 'settings', 'email', $userdata['email']); + } + if ($userdata['quota'] !== null) { + $this->config->setUserValue($userdata['user_id'], 'files', 'quota', $userdata['quota']); + } + if ($userdata['last_login'] !== null) { + $this->config->setUserValue($userdata['user_id'], 'login', 'lastLogin', $userdata['last_login']); + } + if ($state === 1) { + $this->config->setUserValue($userdata['user_id'], 'core', 'enabled', 'true'); + } elseif ($state === 2) { + $this->config->setUserValue($userdata['user_id'], 'core', 'enabled', 'false'); + } + + if ($userdata['display_name'] !== null) { + $update->setParameter('displayname', $userdata['display_name']) + ->setParameter('userid', $userdata['user_id']); + $update->execute(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/RemoveLinkShares.php b/docker/overlays/nextcloud/html/lib/private/Repair/RemoveLinkShares.php new file mode 100644 index 0000000..3a0dd6f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/RemoveLinkShares.php @@ -0,0 +1,242 @@ + + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair; + +use Doctrine\DBAL\Driver\Statement; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OCP\Notification\IManager; + +class RemoveLinkShares implements IRepairStep { + /** @var IDBConnection */ + private $connection; + /** @var IConfig */ + private $config; + /** @var string[] */ + private $userToNotify = []; + /** @var IGroupManager */ + private $groupManager; + /** @var IManager */ + private $notificationManager; + /** @var ITimeFactory */ + private $timeFactory; + + public function __construct(IDBConnection $connection, + IConfig $config, + IGroupManager $groupManager, + IManager $notificationManager, + ITimeFactory $timeFactory) { + $this->connection = $connection; + $this->config = $config; + $this->groupManager = $groupManager; + $this->notificationManager = $notificationManager; + $this->timeFactory = $timeFactory; + } + + + public function getName(): string { + return 'Remove potentially over exposing share links'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + if (version_compare($versionFromBeforeUpdate, '14.0.11', '<')) { + return true; + } + if (version_compare($versionFromBeforeUpdate, '15.0.8', '<')) { + return true; + } + if (version_compare($versionFromBeforeUpdate, '16.0.0', '<=')) { + return true; + } + + return false; + } + + /** + * Delete the share + * + * @param int $id + */ + private function deleteShare(int $id): void { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); + $qb->execute(); + } + + /** + * Get the total of affected shares + * + * @return int + */ + private function getTotal(): int { + $subSubQuery = $this->connection->getQueryBuilder(); + $subSubQuery->select('*') + ->from('share') + ->where($subSubQuery->expr()->isNotNull('parent')) + ->andWhere($subSubQuery->expr()->eq('share_type', $subSubQuery->expr()->literal(3, IQueryBuilder::PARAM_INT))); + + $subQuery = $this->connection->getQueryBuilder(); + $subQuery->select('s1.id') + ->from($subQuery->createFunction('(' . $subSubQuery->getSQL() . ')'), 's1') + ->join( + 's1', 'share', 's2', + $subQuery->expr()->eq('s1.parent', 's2.id') + ) + ->where($subQuery->expr()->orX( + $subQuery->expr()->eq('s2.share_type', $subQuery->expr()->literal(1, IQueryBuilder::PARAM_INT)), + $subQuery->expr()->eq('s2.share_type', $subQuery->expr()->literal(2, IQueryBuilder::PARAM_INT)) + )) + ->andWhere($subQuery->expr()->eq('s1.item_source', 's2.item_source')); + + $query = $this->connection->getQueryBuilder(); + $query->select($query->func()->count('*', 'total')) + ->from('share') + ->where($query->expr()->in('id', $query->createFunction('(' . $subQuery->getSQL() . ')'))); + + $result = $query->execute(); + $data = $result->fetch(); + $result->closeCursor(); + + return (int) $data['total']; + } + + /** + * Get the cursor to fetch all the shares + * + * @return \Doctrine\DBAL\Driver\Statement + */ + private function getShares(): Statement { + $subQuery = $this->connection->getQueryBuilder(); + $subQuery->select('*') + ->from('share') + ->where($subQuery->expr()->isNotNull('parent')) + ->andWhere($subQuery->expr()->eq('share_type', $subQuery->expr()->literal(3, IQueryBuilder::PARAM_INT))); + + $query = $this->connection->getQueryBuilder(); + $query->select('s1.id', 's1.uid_owner', 's1.uid_initiator') + ->from($query->createFunction('(' . $subQuery->getSQL() . ')'), 's1') + ->join( + 's1', 'share', 's2', + $query->expr()->eq('s1.parent', 's2.id') + ) + ->where($query->expr()->orX( + $query->expr()->eq('s2.share_type', $query->expr()->literal(1, IQueryBuilder::PARAM_INT)), + $query->expr()->eq('s2.share_type', $query->expr()->literal(2, IQueryBuilder::PARAM_INT)) + )) + ->andWhere($query->expr()->eq('s1.item_source', 's2.item_source')); + return $query->execute(); + } + + /** + * Process a single share + * + * @param array $data + */ + private function processShare(array $data): void { + $id = $data['id']; + + $this->addToNotify($data['uid_owner']); + $this->addToNotify($data['uid_initiator']); + + $this->deleteShare((int)$id); + } + + /** + * Update list of users to notify + * + * @param string $uid + */ + private function addToNotify(string $uid): void { + if (!isset($this->userToNotify[$uid])) { + $this->userToNotify[$uid] = true; + } + } + + /** + * Send all notifications + */ + private function sendNotification(): void { + $time = $this->timeFactory->getDateTime(); + + $notification = $this->notificationManager->createNotification(); + $notification->setApp('core') + ->setDateTime($time) + ->setObject('repair', 'exposing_links') + ->setSubject('repair_exposing_links'); + + $users = array_keys($this->userToNotify); + foreach ($users as $user) { + $notification->setUser((string) $user); + $this->notificationManager->notify($notification); + } + } + + private function repair(IOutput $output, int $total): void { + $output->startProgress($total); + + $shareCursor = $this->getShares(); + while ($data = $shareCursor->fetch()) { + $this->processShare($data); + $output->advance(); + } + $output->finishProgress(); + $shareCursor->closeCursor(); + + // Notifiy all admins + $adminGroup = $this->groupManager->get('admin'); + $adminUsers = $adminGroup->getUsers(); + foreach ($adminUsers as $user) { + $this->addToNotify($user->getUID()); + } + + $output->info('Sending notifications to admins and affected users'); + $this->sendNotification(); + } + + public function run(IOutput $output): void { + if ($this->shouldRun() === false || ($total = $this->getTotal()) === 0) { + $output->info('No need to remove link shares.'); + return; + } + + $output->info('Removing potentially over exposing link shares'); + $this->repair($output, $total); + $output->info('Removed potentially over exposing link shares'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/RepairInvalidShares.php b/docker/overlays/nextcloud/html/lib/private/Repair/RepairInvalidShares.php new file mode 100644 index 0000000..1fc98a1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/RepairInvalidShares.php @@ -0,0 +1,121 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Repairs shares with invalid data + */ +class RepairInvalidShares implements IRepairStep { + public const CHUNK_SIZE = 200; + + /** @var \OCP\IConfig */ + protected $config; + + /** @var \OCP\IDBConnection */ + protected $connection; + + /** + * @param \OCP\IConfig $config + * @param \OCP\IDBConnection $connection + */ + public function __construct($config, $connection) { + $this->connection = $connection; + $this->config = $config; + } + + public function getName() { + return 'Repair invalid shares'; + } + + /** + * Adjust file share permissions + */ + private function adjustFileSharePermissions(IOutput $out) { + $mask = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_SHARE; + $builder = $this->connection->getQueryBuilder(); + + $permsFunc = $builder->expr()->bitwiseAnd('permissions', $mask); + $builder + ->update('share') + ->set('permissions', $permsFunc) + ->where($builder->expr()->eq('item_type', $builder->expr()->literal('file'))) + ->andWhere($builder->expr()->neq('permissions', $permsFunc)); + + $updatedEntries = $builder->execute(); + if ($updatedEntries > 0) { + $out->info('Fixed file share permissions for ' . $updatedEntries . ' shares'); + } + } + + /** + * Remove shares where the parent share does not exist anymore + */ + private function removeSharesNonExistingParent(IOutput $out) { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('s1.parent') + ->from('share', 's1') + ->where($query->expr()->isNotNull('s1.parent')) + ->andWhere($query->expr()->isNull('s2.id')) + ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id')) + ->groupBy('s1.parent') + ->setMaxResults(self::CHUNK_SIZE); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($deleteQuery->expr()->eq('parent', $deleteQuery->createParameter('parent'))); + + $deletedInLastChunk = self::CHUNK_SIZE; + while ($deletedInLastChunk === self::CHUNK_SIZE) { + $deletedInLastChunk = 0; + $result = $query->execute(); + while ($row = $result->fetch()) { + $deletedInLastChunk++; + $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent']) + ->execute(); + } + $result->closeCursor(); + } + + if ($deletedEntries) { + $out->info('Removed ' . $deletedEntries . ' shares where the parent did not exist'); + } + } + + public function run(IOutput $out) { + $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + if (version_compare($ocVersionFromBeforeUpdate, '12.0.0.11', '<')) { + $this->adjustFileSharePermissions($out); + } + + $this->removeSharesNonExistingParent($out); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/RepairMimeTypes.php b/docker/overlays/nextcloud/html/lib/private/Repair/RepairMimeTypes.php new file mode 100644 index 0000000..60a7df2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/RepairMimeTypes.php @@ -0,0 +1,246 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Olivier Paroz + * @author Rello + * @author Roeland Jago Douma + * @author Stefan Weil + * @author Thomas Ebert + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class RepairMimeTypes implements IRepairStep { + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var int + */ + protected $folderMimeTypeId; + + /** + * @param \OCP\IConfig $config + */ + public function __construct($config) { + $this->config = $config; + } + + public function getName() { + return 'Repair mime types'; + } + + private static function existsStmt() { + return \OC_DB::prepare(' + SELECT count(`mimetype`) + FROM `*PREFIX*mimetypes` + WHERE `mimetype` = ? + '); + } + + private static function getIdStmt() { + return \OC_DB::prepare(' + SELECT `id` + FROM `*PREFIX*mimetypes` + WHERE `mimetype` = ? + '); + } + + private static function insertStmt() { + return \OC_DB::prepare(' + INSERT INTO `*PREFIX*mimetypes` ( `mimetype` ) + VALUES ( ? ) + '); + } + + private static function updateByNameStmt() { + return \OC_DB::prepare(' + UPDATE `*PREFIX*filecache` + SET `mimetype` = ? + WHERE `mimetype` <> ? AND `mimetype` <> ? AND `name` ILIKE ? + '); + } + + private function updateMimetypes($updatedMimetypes) { + if (empty($this->folderMimeTypeId)) { + $result = \OC_DB::executeAudited(self::getIdStmt(), ['httpd/unix-directory']); + $this->folderMimeTypeId = (int)$result->fetchOne(); + } + + $count = 0; + foreach ($updatedMimetypes as $extension => $mimetype) { + $result = \OC_DB::executeAudited(self::existsStmt(), [$mimetype]); + $exists = $result->fetchOne(); + + if (!$exists) { + // insert mimetype + \OC_DB::executeAudited(self::insertStmt(), [$mimetype]); + } + + // get target mimetype id + $result = \OC_DB::executeAudited(self::getIdStmt(), [$mimetype]); + $mimetypeId = $result->fetchOne(); + + // change mimetype for files with x extension + $count += \OC_DB::executeAudited(self::updateByNameStmt(), [$mimetypeId, $this->folderMimeTypeId, $mimetypeId, '%.' . $extension]); + } + + return $count; + } + + private function introduceImageTypes() { + $updatedMimetypes = [ + 'jp2' => 'image/jp2', + 'webp' => 'image/webp', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceWindowsProgramTypes() { + $updatedMimetypes = [ + 'htaccess' => 'text/plain', + 'bat' => 'application/x-msdos-program', + 'cmd' => 'application/cmd', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceLocationTypes() { + $updatedMimetypes = [ + 'gpx' => 'application/gpx+xml', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'tcx' => 'application/vnd.garmin.tcx+xml', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceInternetShortcutTypes() { + $updatedMimetypes = [ + 'url' => 'application/internet-shortcut', + 'webloc' => 'application/internet-shortcut' + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceStreamingTypes() { + $updatedMimetypes = [ + 'm3u' => 'audio/mpegurl', + 'm3u8' => 'audio/mpegurl', + 'pls' => 'audio/x-scpls' + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceVisioTypes() { + $updatedMimetypes = [ + 'vsdm' => 'application/vnd.visio', + 'vsdx' => 'application/vnd.visio', + 'vssm' => 'application/vnd.visio', + 'vssx' => 'application/vnd.visio', + 'vstm' => 'application/vnd.visio', + 'vstx' => 'application/vnd.visio', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceComicbookTypes() { + $updatedMimetypes = [ + 'cb7' => 'application/comicbook+7z', + 'cba' => 'application/comicbook+ace', + 'cbr' => 'application/comicbook+rar', + 'cbt' => 'application/comicbook+tar', + 'cbtc' => 'application/comicbook+truecrypt', + 'cbz' => 'application/comicbook+zip', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceOpenDocumentTemplates() { + $updatedMimetypes = [ + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + /** + * Fix mime types + */ + public function run(IOutput $out) { + $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + // NOTE TO DEVELOPERS: when adding new mime types, please make sure to + // add a version comparison to avoid doing it every time + + if (version_compare($ocVersionFromBeforeUpdate, '12.0.0.14', '<') && $this->introduceImageTypes()) { + $out->info('Fixed image mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '12.0.0.13', '<') && $this->introduceWindowsProgramTypes()) { + $out->info('Fixed windows program mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '13.0.0.0', '<') && $this->introduceLocationTypes()) { + $out->info('Fixed geospatial mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '13.0.0.3', '<') && $this->introduceInternetShortcutTypes()) { + $out->info('Fixed internet-shortcut mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '13.0.0.6', '<') && $this->introduceStreamingTypes()) { + $out->info('Fixed streaming mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '14.0.0.8', '<') && $this->introduceVisioTypes()) { + $out->info('Fixed visio mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '14.0.0.10', '<') && $this->introduceComicbookTypes()) { + $out->info('Fixed comicbook mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '20.0.0.5', '<') && $this->introduceOpenDocumentTemplates()) { + $out->info('Fixed OpenDocument template mime types'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Repair/SqliteAutoincrement.php b/docker/overlays/nextcloud/html/lib/private/Repair/SqliteAutoincrement.php new file mode 100644 index 0000000..4bdd7c7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Repair/SqliteAutoincrement.php @@ -0,0 +1,101 @@ + + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\SchemaDiff; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Schema\TableDiff; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Fixes Sqlite autoincrement by forcing the SQLite table schemas to be + * altered in order to retrigger SQL schema generation through OCSqlitePlatform. + */ +class SqliteAutoincrement implements IRepairStep { + /** + * @var \OC\DB\Connection + */ + protected $connection; + + /** + * @param \OC\DB\Connection $connection + */ + public function __construct($connection) { + $this->connection = $connection; + } + + public function getName() { + return 'Repair SQLite autoincrement'; + } + + /** + * Fix mime types + */ + public function run(IOutput $out) { + if (!$this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + return; + } + + $sourceSchema = $this->connection->getSchemaManager()->createSchema(); + + $schemaDiff = new SchemaDiff(); + + foreach ($sourceSchema->getTables() as $tableSchema) { + $primaryKey = $tableSchema->getPrimaryKey(); + if (!$primaryKey) { + continue; + } + + $columnNames = $primaryKey->getColumns(); + + // add a column diff for every primary key column, + // but do not actually change anything, this will + // force the generation of SQL statements to alter + // those tables, which will then trigger the + // specific SQL code from OCSqlitePlatform + try { + $tableDiff = new TableDiff($tableSchema->getName()); + $tableDiff->fromTable = $tableSchema; + foreach ($columnNames as $columnName) { + $columnSchema = $tableSchema->getColumn($columnName); + $columnDiff = new ColumnDiff($columnSchema->getName(), $columnSchema); + $tableDiff->changedColumns[] = $columnDiff; + $schemaDiff->changedTables[] = $tableDiff; + } + } catch (SchemaException $e) { + // ignore + } + } + + $this->connection->beginTransaction(); + foreach ($schemaDiff->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->query($sql); + } + $this->connection->commit(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/RepairException.php b/docker/overlays/nextcloud/html/lib/private/RepairException.php new file mode 100644 index 0000000..002ec36 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/RepairException.php @@ -0,0 +1,31 @@ + + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +/** + * Exception thrown whenever a database/migration repair + * could not be done. + */ +class RepairException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/RichObjectStrings/Validator.php b/docker/overlays/nextcloud/html/lib/private/RichObjectStrings/Validator.php new file mode 100644 index 0000000..c80da29 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/RichObjectStrings/Validator.php @@ -0,0 +1,123 @@ + + * + * @author Joas Schilling + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\RichObjectStrings; + +use OCP\RichObjectStrings\Definitions; +use OCP\RichObjectStrings\InvalidObjectExeption; +use OCP\RichObjectStrings\IValidator; + +/** + * Class Validator + * + * @package OCP\RichObjectStrings + * @since 11.0.0 + */ +class Validator implements IValidator { + + /** @var Definitions */ + protected $definitions; + + /** @var array[] */ + protected $requiredParameters = []; + + /** + * Constructor + * + * @param Definitions $definitions + */ + public function __construct(Definitions $definitions) { + $this->definitions = $definitions; + } + + /** + * @param string $subject + * @param array[] $parameters + * @throws InvalidObjectExeption + * @since 11.0.0 + */ + public function validate($subject, array $parameters) { + $matches = []; + $result = preg_match_all('/\{([a-z0-9]+)\}/i', $subject, $matches); + + if ($result === false) { + throw new InvalidObjectExeption(); + } + + if (!empty($matches[1])) { + foreach ($matches[1] as $parameter) { + if (!isset($parameters[$parameter])) { + throw new InvalidObjectExeption('Parameter is undefined'); + } + } + } + + foreach ($parameters as $parameter) { + if (!\is_array($parameter)) { + throw new InvalidObjectExeption('Parameter is malformed'); + } + + $this->validateParameter($parameter); + } + } + + /** + * @param array $parameter + * @throws InvalidObjectExeption + */ + protected function validateParameter(array $parameter) { + if (!isset($parameter['type'])) { + throw new InvalidObjectExeption('Object type is undefined'); + } + + $definition = $this->definitions->getDefinition($parameter['type']); + $requiredParameters = $this->getRequiredParameters($parameter['type'], $definition); + + $missingKeys = array_diff($requiredParameters, array_keys($parameter)); + if (!empty($missingKeys)) { + throw new InvalidObjectExeption('Object is invalid'); + } + } + + /** + * @param string $type + * @param array $definition + * @return string[] + */ + protected function getRequiredParameters($type, array $definition) { + if (isset($this->requiredParameters[$type])) { + return $this->requiredParameters[$type]; + } + + $this->requiredParameters[$type] = []; + foreach ($definition['parameters'] as $parameter => $data) { + if ($data['required']) { + $this->requiredParameters[$type][] = $parameter; + } + } + + return $this->requiredParameters[$type]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Route/CachingRouter.php b/docker/overlays/nextcloud/html/lib/private/Route/CachingRouter.php new file mode 100644 index 0000000..e94039c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Route/CachingRouter.php @@ -0,0 +1,67 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Route; + +use OCP\ILogger; + +class CachingRouter extends Router { + /** + * @var \OCP\ICache + */ + protected $cache; + + /** + * @param \OCP\ICache $cache + * @param ILogger $logger + */ + public function __construct($cache, ILogger $logger) { + $this->cache = $cache; + parent::__construct($logger); + } + + /** + * Generate url based on $name and $parameters + * + * @param string $name Name of the route to use. + * @param array $parameters Parameters for the route + * @param bool $absolute + * @return string + */ + public function generate($name, $parameters = [], $absolute = false) { + asort($parameters); + $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . $name . sha1(json_encode($parameters)) . (int)$absolute; + $cachedKey = $this->cache->get($key); + if ($cachedKey) { + return $cachedKey; + } else { + $url = parent::generate($name, $parameters, $absolute); + if ($url) { + $this->cache->set($key, $url, 3600); + } + return $url; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Route/Route.php b/docker/overlays/nextcloud/html/lib/private/Route/Route.php new file mode 100644 index 0000000..705183d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Route/Route.php @@ -0,0 +1,160 @@ + + * @author Christoph Wurst + * @author David Prévot + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Route; + +use OCP\Route\IRoute; +use Symfony\Component\Routing\Route as SymfonyRoute; + +class Route extends SymfonyRoute implements IRoute { + /** + * Specify the method when this route is to be used + * + * @param string $method HTTP method (uppercase) + * @return \OC\Route\Route + */ + public function method($method) { + $this->setMethods($method); + return $this; + } + + /** + * Specify POST as the method to use with this route + * @return \OC\Route\Route + */ + public function post() { + $this->method('POST'); + return $this; + } + + /** + * Specify GET as the method to use with this route + * @return \OC\Route\Route + */ + public function get() { + $this->method('GET'); + return $this; + } + + /** + * Specify PUT as the method to use with this route + * @return \OC\Route\Route + */ + public function put() { + $this->method('PUT'); + return $this; + } + + /** + * Specify DELETE as the method to use with this route + * @return \OC\Route\Route + */ + public function delete() { + $this->method('DELETE'); + return $this; + } + + /** + * Specify PATCH as the method to use with this route + * @return \OC\Route\Route + */ + public function patch() { + $this->method('PATCH'); + return $this; + } + + /** + * Defaults to use for this route + * + * @param array $defaults The defaults + * @return \OC\Route\Route + */ + public function defaults($defaults) { + $action = $this->getDefault('action'); + $this->setDefaults($defaults); + if (isset($defaults['action'])) { + $action = $defaults['action']; + } + $this->action($action); + return $this; + } + + /** + * Requirements for this route + * + * @param array $requirements The requirements + * @return \OC\Route\Route + */ + public function requirements($requirements) { + $method = $this->getMethods(); + $this->setRequirements($requirements); + if (isset($requirements['_method'])) { + $method = $requirements['_method']; + } + if ($method) { + $this->method($method); + } + return $this; + } + + /** + * The action to execute when this route matches + * + * @param string|callable $class the class or a callable + * @param string $function the function to use with the class + * @return \OC\Route\Route + * + * This function is called with $class set to a callable or + * to the class with $function + */ + public function action($class, $function = null) { + $action = [$class, $function]; + if (is_null($function)) { + $action = $class; + } + $this->setDefault('action', $action); + return $this; + } + + /** + * The action to execute when this route matches, includes a file like + * it is called directly + * @param string $file + * @return void + */ + public function actionInclude($file) { + $function = function ($param) use ($file) { + unset($param["_route"]); + $_GET=array_merge($_GET, $param); + unset($param); + require_once "$file"; + } ; + $this->action($function); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Route/Router.php b/docker/overlays/nextcloud/html/lib/private/Route/Router.php new file mode 100644 index 0000000..94c637e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Route/Router.php @@ -0,0 +1,447 @@ + + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Route; + +use OCP\AppFramework\App; +use OCP\ILogger; +use OCP\Route\IRouter; +use OCP\Util; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; + +class Router implements IRouter { + /** @var RouteCollection[] */ + protected $collections = []; + /** @var null|RouteCollection */ + protected $collection = null; + /** @var null|string */ + protected $collectionName = null; + /** @var null|RouteCollection */ + protected $root = null; + /** @var null|UrlGenerator */ + protected $generator = null; + /** @var string[] */ + protected $routingFiles; + /** @var bool */ + protected $loaded = false; + /** @var array */ + protected $loadedApps = []; + /** @var ILogger */ + protected $logger; + /** @var RequestContext */ + protected $context; + + /** + * @param ILogger $logger + */ + public function __construct(ILogger $logger) { + $this->logger = $logger; + $baseUrl = \OC::$WEBROOT; + if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { + $baseUrl .= '/index.php'; + } + if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) { + $method = $_SERVER['REQUEST_METHOD']; + } else { + $method = 'GET'; + } + $request = \OC::$server->getRequest(); + $host = $request->getServerHost(); + $schema = $request->getServerProtocol(); + $this->context = new RequestContext($baseUrl, $method, $host, $schema); + // TODO cache + $this->root = $this->getCollection('root'); + } + + /** + * Get the files to load the routes from + * + * @return string[] + */ + public function getRoutingFiles() { + if (!isset($this->routingFiles)) { + $this->routingFiles = []; + foreach (\OC_APP::getEnabledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if ($appPath !== false) { + $file = $appPath . '/appinfo/routes.php'; + if (file_exists($file)) { + $this->routingFiles[$app] = $file; + } + } + } + } + return $this->routingFiles; + } + + /** + * Loads the routes + * + * @param null|string $app + */ + public function loadRoutes($app = null) { + if (is_string($app)) { + $app = \OC_App::cleanAppId($app); + } + + $requestedApp = $app; + if ($this->loaded) { + return; + } + if (is_null($app)) { + $this->loaded = true; + $routingFiles = $this->getRoutingFiles(); + } else { + if (isset($this->loadedApps[$app])) { + return; + } + $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; + if ($file !== false && file_exists($file)) { + $routingFiles = [$app => $file]; + } else { + $routingFiles = []; + } + } + \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); + foreach ($routingFiles as $app => $file) { + if (!isset($this->loadedApps[$app])) { + if (!\OC_App::isAppLoaded($app)) { + // app MUST be loaded before app routes + // try again next time loadRoutes() is called + $this->loaded = false; + continue; + } + $this->loadedApps[$app] = true; + $this->useCollection($app); + $this->requireRouteFile($file, $app); + $collection = $this->getCollection($app); + $this->root->addCollection($collection); + + // Also add the OCS collection + $collection = $this->getCollection($app.'.ocs'); + $collection->addPrefix('/ocsapp'); + $this->root->addCollection($collection); + } + } + if (!isset($this->loadedApps['core'])) { + $this->loadedApps['core'] = true; + $this->useCollection('root'); + require_once __DIR__ . '/../../../core/routes.php'; + + // Also add the OCS collection + $collection = $this->getCollection('root.ocs'); + $collection->addPrefix('/ocsapp'); + $this->root->addCollection($collection); + } + if ($this->loaded) { + $collection = $this->getCollection('ocs'); + $collection->addPrefix('/ocs'); + $this->root->addCollection($collection); + } + \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); + } + + /** + * @return string + * @deprecated + */ + public function getCacheKey() { + return ''; + } + + /** + * @param string $name + * @return \Symfony\Component\Routing\RouteCollection + */ + protected function getCollection($name) { + if (!isset($this->collections[$name])) { + $this->collections[$name] = new RouteCollection(); + } + return $this->collections[$name]; + } + + /** + * Sets the collection to use for adding routes + * + * @param string $name Name of the collection to use. + * @return void + */ + public function useCollection($name) { + $this->collection = $this->getCollection($name); + $this->collectionName = $name; + } + + /** + * returns the current collection name in use for adding routes + * + * @return string the collection name + */ + public function getCurrentCollection() { + return $this->collectionName; + } + + + /** + * Create a \OC\Route\Route. + * + * @param string $name Name of the route to create. + * @param string $pattern The pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @return \OC\Route\Route + */ + public function create($name, + $pattern, + array $defaults = [], + array $requirements = []) { + $route = new Route($pattern, $defaults, $requirements); + $this->collection->add($name, $route); + return $route; + } + + /** + * Find the route matching $url + * + * @param string $url The url to find + * @throws \Exception + * @return array + */ + public function findMatchingRoute(string $url): array { + if (substr($url, 0, 6) === '/apps/') { + // empty string / 'apps' / $app / rest of the route + list(, , $app,) = explode('/', $url, 4); + + $app = \OC_App::cleanAppId($app); + \OC::$REQUESTEDAPP = $app; + $this->loadRoutes($app); + } elseif (substr($url, 0, 13) === '/ocsapp/apps/') { + // empty string / 'ocsapp' / 'apps' / $app / rest of the route + list(, , , $app,) = explode('/', $url, 5); + + $app = \OC_App::cleanAppId($app); + \OC::$REQUESTEDAPP = $app; + $this->loadRoutes($app); + } elseif (substr($url, 0, 10) === '/settings/') { + $this->loadRoutes('settings'); + } elseif (substr($url, 0, 6) === '/core/') { + \OC::$REQUESTEDAPP = $url; + if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) { + \OC_App::loadApps(); + } + $this->loadRoutes('core'); + } else { + $this->loadRoutes(); + } + + $matcher = new UrlMatcher($this->root, $this->context); + try { + $parameters = $matcher->match($url); + } catch (ResourceNotFoundException $e) { + if (substr($url, -1) !== '/') { + // We allow links to apps/files? for backwards compatibility reasons + // However, since Symfony does not allow empty route names, the route + // we need to match is '/', so we need to append the '/' here. + try { + $parameters = $matcher->match($url . '/'); + } catch (ResourceNotFoundException $newException) { + // If we still didn't match a route, we throw the original exception + throw $e; + } + } else { + throw $e; + } + } + + return $parameters; + } + + /** + * Find and execute the route matching $url + * + * @param string $url The url to find + * @throws \Exception + * @return void + */ + public function match($url) { + $parameters = $this->findMatchingRoute($url); + + \OC::$server->getEventLogger()->start('run_route', 'Run route'); + if (isset($parameters['caller'])) { + $caller = $parameters['caller']; + unset($parameters['caller']); + $application = $this->getApplicationClass($caller[0]); + \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters); + } elseif (isset($parameters['action'])) { + $action = $parameters['action']; + if (!is_callable($action)) { + throw new \Exception('not a callable action'); + } + unset($parameters['action']); + call_user_func($action, $parameters); + } elseif (isset($parameters['file'])) { + include $parameters['file']; + } else { + throw new \Exception('no action available'); + } + \OC::$server->getEventLogger()->end('run_route'); + } + + /** + * Get the url generator + * + * @return \Symfony\Component\Routing\Generator\UrlGenerator + * + */ + public function getGenerator() { + if (null !== $this->generator) { + return $this->generator; + } + + return $this->generator = new UrlGenerator($this->root, $this->context); + } + + /** + * Generate url based on $name and $parameters + * + * @param string $name Name of the route to use. + * @param array $parameters Parameters for the route + * @param bool $absolute + * @return string + */ + public function generate($name, + $parameters = [], + $absolute = false) { + $referenceType = UrlGenerator::ABSOLUTE_URL; + if ($absolute === false) { + $referenceType = UrlGenerator::ABSOLUTE_PATH; + } + $name = $this->fixLegacyRootName($name); + if (strpos($name, '.') !== false) { + list($appName, $other) = explode('.', $name, 3); + // OCS routes are prefixed with "ocs." + if ($appName === 'ocs') { + $appName = $other; + } + $this->loadRoutes($appName); + try { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } catch (RouteNotFoundException $e) { + } + } + + // Fallback load all routes + $this->loadRoutes(); + try { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } catch (RouteNotFoundException $e) { + $this->logger->logException($e, ['level' => ILogger::INFO]); + return ''; + } + } + + protected function fixLegacyRootName(string $routeName): string { + if ($routeName === 'files.viewcontroller.showFile') { + return 'files.View.showFile'; + } + if ($routeName === 'files_sharing.sharecontroller.showShare') { + return 'files_sharing.Share.showShare'; + } + if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') { + return 'files_sharing.Share.showAuthenticate'; + } + if ($routeName === 'files_sharing.sharecontroller.authenticate') { + return 'files_sharing.Share.authenticate'; + } + if ($routeName === 'files_sharing.sharecontroller.downloadShare') { + return 'files_sharing.Share.downloadShare'; + } + if ($routeName === 'files_sharing.publicpreview.directLink') { + return 'files_sharing.PublicPreview.directLink'; + } + if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') { + return 'cloud_federation_api.RequestHandler.addShare'; + } + if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') { + return 'cloud_federation_api.RequestHandler.receiveNotification'; + } + return $routeName; + } + + /** + * To isolate the variable scope used inside the $file it is required in it's own method + * + * @param string $file the route file location to include + * @param string $appName + */ + private function requireRouteFile($file, $appName) { + $this->setupRoutes(include_once $file, $appName); + } + + + /** + * If a routes.php file returns an array, try to set up the application and + * register the routes for the app. The application class will be chosen by + * camelcasing the appname, e.g.: my_app will be turned into + * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default + * App will be intialized. This makes it optional to ship an + * appinfo/application.php by using the built in query resolver + * + * @param array $routes the application routes + * @param string $appName the name of the app. + */ + private function setupRoutes($routes, $appName) { + if (is_array($routes)) { + $application = $this->getApplicationClass($appName); + $application->registerRoutes($this, $routes); + } + } + + private function getApplicationClass(string $appName) { + $appNameSpace = App::buildAppNamespace($appName); + + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + + if (class_exists($applicationClassName)) { + $application = \OC::$server->query($applicationClassName); + } else { + $application = new App($appName); + } + + return $application; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search.php b/docker/overlays/nextcloud/html/lib/private/Search.php new file mode 100644 index 0000000..9ecf34a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search.php @@ -0,0 +1,120 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\ISearch; +use OCP\Search\PagedProvider; +use OCP\Search\Provider; + +/** + * Provide an interface to all search providers + */ +class Search implements ISearch { + /** @var Provider[] */ + private $providers = []; + private $registeredProviders = []; + + /** + * Search all providers for $query + * @param string $query + * @param string[] $inApps optionally limit results to the given apps + * @param int $page pages start at page 1 + * @param int $size, 0 = all + * @return array An array of OC\Search\Result's + */ + public function searchPaged($query, array $inApps = [], $page = 1, $size = 30) { + $this->initProviders(); + $results = []; + foreach ($this->providers as $provider) { + if (! $provider->providesResultsFor($inApps)) { + continue; + } + if ($provider instanceof PagedProvider) { + $results = array_merge($results, $provider->searchPaged($query, $page, $size)); + } elseif ($provider instanceof Provider) { + $providerResults = $provider->search($query); + if ($size > 0) { + $slicedResults = array_slice($providerResults, ($page - 1) * $size, $size); + $results = array_merge($results, $slicedResults); + } else { + $results = array_merge($results, $providerResults); + } + } else { + \OC::$server->getLogger()->warning('Ignoring Unknown search provider', ['provider' => $provider]); + } + } + return $results; + } + + /** + * Remove all registered search providers + */ + public function clearProviders() { + $this->providers = []; + $this->registeredProviders = []; + } + + /** + * Remove one existing search provider + * @param string $provider class name of a OC\Search\Provider + */ + public function removeProvider($provider) { + $this->registeredProviders = array_filter( + $this->registeredProviders, + function ($element) use ($provider) { + return ($element['class'] != $provider); + } + ); + // force regeneration of providers on next search + $this->providers = []; + } + + /** + * Register a new search provider to search with + * @param string $class class name of a OC\Search\Provider + * @param array $options optional + */ + public function registerProvider($class, array $options = []) { + $this->registeredProviders[] = ['class' => $class, 'options' => $options]; + } + + /** + * Create instances of all the registered search providers + */ + private function initProviders() { + if (! empty($this->providers)) { + return; + } + foreach ($this->registeredProviders as $provider) { + $class = $provider['class']; + $options = $provider['options']; + $this->providers[] = new $class($options); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/Provider/File.php b/docker/overlays/nextcloud/html/lib/private/Search/Provider/File.php new file mode 100644 index 0000000..e2ae328 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/Provider/File.php @@ -0,0 +1,81 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Jakob Sack + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Search\Provider; + +use OC\Files\Filesystem; + +/** + * Provide search results from the 'files' app + * @deprecated 20.0.0 + */ +class File extends \OCP\Search\Provider { + + /** + * Search for files and folders matching the given query + * @param string $query + * @return \OCP\Search\Result[] + * @deprecated 20.0.0 + */ + public function search($query) { + $files = Filesystem::search($query); + $results = []; + // edit results + foreach ($files as $fileData) { + // skip versions + if (strpos($fileData['path'], '_versions') === 0) { + continue; + } + // skip top-level folder + if ($fileData['name'] === 'files' && $fileData['parent'] === -1) { + continue; + } + // create audio result + if ($fileData['mimepart'] === 'audio') { + $result = new \OC\Search\Result\Audio($fileData); + } + // create image result + elseif ($fileData['mimepart'] === 'image') { + $result = new \OC\Search\Result\Image($fileData); + } + // create folder result + elseif ($fileData['mimetype'] === 'httpd/unix-directory') { + $result = new \OC\Search\Result\Folder($fileData); + } + // or create file result + else { + $result = new \OC\Search\Result\File($fileData); + } + // add to results + $results[] = $result; + } + // return + return $results; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/Result/Audio.php b/docker/overlays/nextcloud/html/lib/private/Search/Result/Audio.php new file mode 100644 index 0000000..e3917b7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/Result/Audio.php @@ -0,0 +1,44 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Search\Result; + +/** + * A found audio file + * @deprecated 20.0.0 + */ +class Audio extends File { + + /** + * Type name; translated in templates + * @var string + * @deprecated 20.0.0 + */ + public $type = 'audio'; + + /** + * @TODO add ID3 information + */ +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/Result/File.php b/docker/overlays/nextcloud/html/lib/private/Search/Result/File.php new file mode 100644 index 0000000..33e1e97 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/Result/File.php @@ -0,0 +1,150 @@ + + * @author Christoph Wurst + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Search\Result; + +use OCP\Files\FileInfo; +use OCP\Files\Folder; +use OCP\IPreview; +use OCP\IUserSession; + +/** + * A found file + * @deprecated 20.0.0 + */ +class File extends \OCP\Search\Result { + + /** + * Type name; translated in templates + * @var string + * @deprecated 20.0.0 + */ + public $type = 'file'; + + /** + * Path to file + * @var string + * @deprecated 20.0.0 + */ + public $path; + + /** + * Size, in bytes + * @var int + * @deprecated 20.0.0 + */ + public $size; + + /** + * Date modified, in human readable form + * @var string + * @deprecated 20.0.0 + */ + public $modified; + + /** + * File mime type + * @var string + * @deprecated 20.0.0 + */ + public $mime_type; + + /** + * File permissions: + * + * @var string + * @deprecated 20.0.0 + */ + public $permissions; + + /** + * Has a preview + * + * @var string + * @deprecated 20.0.0 + */ + public $has_preview; + + /** + * Create a new file search result + * @param FileInfo $data file data given by provider + * @deprecated 20.0.0 + */ + public function __construct(FileInfo $data) { + $path = $this->getRelativePath($data->getPath()); + + $info = pathinfo($path); + $this->id = $data->getId(); + $this->name = $info['basename']; + $this->link = \OC::$server->getURLGenerator()->linkToRoute( + 'files.view.index', + [ + 'dir' => $info['dirname'], + 'scrollto' => $info['basename'], + ] + ); + $this->permissions = $data->getPermissions(); + $this->path = $path; + $this->size = $data->getSize(); + $this->modified = $data->getMtime(); + $this->mime_type = $data->getMimetype(); + $this->has_preview = $this->hasPreview($data); + } + + /** + * @var Folder $userFolderCache + * @deprecated 20.0.0 + */ + protected static $userFolderCache = null; + + /** + * converts a path relative to the users files folder + * eg /user/files/foo.txt -> /foo.txt + * @param string $path + * @return string relative path + * @deprecated 20.0.0 + */ + protected function getRelativePath($path) { + if (!isset(self::$userFolderCache)) { + $userSession = \OC::$server->get(IUserSession::class); + $userID = $userSession->getUser()->getUID(); + self::$userFolderCache = \OC::$server->getUserFolder($userID); + } + return self::$userFolderCache->getRelativePath($path); + } + + /** + * Is the preview available + * @param FileInfo $data + * @return bool + * @deprecated 20.0.0 + */ + protected function hasPreview($data) { + $previewManager = \OC::$server->get(IPreview::class); + return $previewManager->isAvailable($data); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/Result/Folder.php b/docker/overlays/nextcloud/html/lib/private/Search/Result/Folder.php new file mode 100644 index 0000000..1268b13 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/Result/Folder.php @@ -0,0 +1,40 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Search\Result; + +/** + * A found folder + * @deprecated 20.0.0 + */ +class Folder extends File { + + /** + * Type name; translated in templates + * @var string + * @deprecated 20.0.0 + */ + public $type = 'folder'; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/Result/Image.php b/docker/overlays/nextcloud/html/lib/private/Search/Result/Image.php new file mode 100644 index 0000000..5a46138 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/Result/Image.php @@ -0,0 +1,44 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Search\Result; + +/** + * A found image file + * @deprecated 20.0.0 + */ +class Image extends File { + + /** + * Type name; translated in templates + * @var string + * @deprecated 20.0.0 + */ + public $type = 'image'; + + /** + * @TODO add EXIF information + */ +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/SearchComposer.php b/docker/overlays/nextcloud/html/lib/private/Search/SearchComposer.php new file mode 100644 index 0000000..5290c2a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/SearchComposer.php @@ -0,0 +1,164 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Search; + +use InvalidArgumentException; +use OC\AppFramework\Bootstrap\Coordinator; +use OCP\AppFramework\QueryException; +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\IUser; +use OCP\Search\IProvider; +use OCP\Search\ISearchQuery; +use OCP\Search\SearchResult; +use function array_map; + +/** + * Queries individual \OCP\Search\IProvider implementations and composes a + * unified search result for the user's search term + * + * The search process is generally split into two steps + * + * 1. Get a list of provider (`getProviders`) + * 2. Get search results of each provider (`search`) + * + * The reasoning behind this is that the runtime complexity of a combined search + * result would be O(n) and linearly grow with each provider added. This comes + * from the nature of php where we can't concurrently fetch the search results. + * So we offload the concurrency the client application (e.g. JavaScript in the + * browser) and let it first get the list of providers to then fetch all results + * concurrently. The client is free to decide whether all concurrent search + * results are awaited or shown as they come in. + * + * @see IProvider::search() for the arguments of the individual search requests + */ +class SearchComposer { + + /** @var IProvider[] */ + private $providers = []; + + /** @var Coordinator */ + private $bootstrapCoordinator; + + /** @var IServerContainer */ + private $container; + + /** @var ILogger */ + private $logger; + + public function __construct(Coordinator $bootstrapCoordinator, + IServerContainer $container, + ILogger $logger) { + $this->container = $container; + $this->logger = $logger; + $this->bootstrapCoordinator = $bootstrapCoordinator; + } + + /** + * Load all providers dynamically that were registered through `registerProvider` + * + * If a provider can't be loaded we log it but the operation continues nevertheless + */ + private function loadLazyProviders(): void { + $context = $this->bootstrapCoordinator->getRegistrationContext(); + if ($context === null) { + // Too early, nothing registered yet + return; + } + + $registrations = $context->getSearchProviders(); + foreach ($registrations as $registration) { + try { + /** @var IProvider $provider */ + $provider = $this->container->query($registration['class']); + $this->providers[$provider->getId()] = $provider; + } catch (QueryException $e) { + // Log an continue. We can be fault tolerant here. + $this->logger->logException($e, [ + 'message' => 'Could not load search provider dynamically: ' . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + } + } + } + + /** + * Get a list of all provider IDs & Names for the consecutive calls to `search` + * Sort the list by the order property + * + * @param string $route the route the user is currently at + * @param array $routeParameters the parameters of the route the user is currently at + * + * @return array + */ + public function getProviders(string $route, array $routeParameters): array { + $this->loadLazyProviders(); + + $providers = array_values( + array_map(function (IProvider $provider) use ($route, $routeParameters) { + return [ + 'id' => $provider->getId(), + 'name' => $provider->getName(), + 'order' => $provider->getOrder($route, $routeParameters), + ]; + }, $this->providers) + ); + + usort($providers, function ($provider1, $provider2) { + return $provider1['order'] <=> $provider2['order']; + }); + + /** + * Return an array with the IDs, but strip the associative keys + */ + return $providers; + } + + /** + * Query an individual search provider for results + * + * @param IUser $user + * @param string $providerId one of the IDs received by `getProviders` + * @param ISearchQuery $query + * + * @return SearchResult + * @throws InvalidArgumentException when the $providerId does not correspond to a registered provider + */ + public function search(IUser $user, + string $providerId, + ISearchQuery $query): SearchResult { + $this->loadLazyProviders(); + + $provider = $this->providers[$providerId] ?? null; + if ($provider === null) { + throw new InvalidArgumentException("Provider $providerId is unknown"); + } + return $provider->search($user, $query); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Search/SearchQuery.php b/docker/overlays/nextcloud/html/lib/private/Search/SearchQuery.php new file mode 100644 index 0000000..728802b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Search/SearchQuery.php @@ -0,0 +1,117 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Search; + +use OCP\Search\ISearchQuery; + +class SearchQuery implements ISearchQuery { + public const LIMIT_DEFAULT = 5; + + /** @var string */ + private $term; + + /** @var int */ + private $sortOrder; + + /** @var int */ + private $limit; + + /** @var int|string|null */ + private $cursor; + + /** @var string */ + private $route; + + /** @var array */ + private $routeParameters; + + /** + * @param string $term + * @param int $sortOrder + * @param int $limit + * @param int|string|null $cursor + * @param string $route + * @param array $routeParameters + */ + public function __construct(string $term, + int $sortOrder = ISearchQuery::SORT_DATE_DESC, + int $limit = self::LIMIT_DEFAULT, + $cursor = null, + string $route = '', + array $routeParameters = []) { + $this->term = $term; + $this->sortOrder = $sortOrder; + $this->limit = $limit; + $this->cursor = $cursor; + $this->route = $route; + $this->routeParameters = $routeParameters; + } + + /** + * @inheritDoc + */ + public function getTerm(): string { + return $this->term; + } + + /** + * @inheritDoc + */ + public function getSortOrder(): int { + return $this->sortOrder; + } + + /** + * @inheritDoc + */ + public function getLimit(): int { + return $this->limit; + } + + /** + * @inheritDoc + */ + public function getCursor() { + return $this->cursor; + } + + /** + * @inheritDoc + */ + public function getRoute(): string { + return $this->route; + } + + /** + * @inheritDoc + */ + public function getRouteParameters(): array { + return $this->routeParameters; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Capabilities.php b/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Capabilities.php new file mode 100644 index 0000000..eab55db --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Capabilities.php @@ -0,0 +1,55 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\Bruteforce; + +use OCP\Capabilities\IPublicCapability; +use OCP\IRequest; + +class Capabilities implements IPublicCapability { + /** @var IRequest */ + private $request; + + /** @var Throttler */ + private $throttler; + + /** + * Capabilities constructor. + * + * @param IRequest $request + * @param Throttler $throttler + */ + public function __construct(IRequest $request, + Throttler $throttler) { + $this->request = $request; + $this->throttler = $throttler; + } + + public function getCapabilities() { + return [ + 'bruteforce' => [ + 'delay' => $this->throttler->getDelay($this->request->getRemoteAddress()) + ] + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Throttler.php b/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Throttler.php new file mode 100644 index 0000000..01c0e02 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Bruteforce/Throttler.php @@ -0,0 +1,355 @@ + + * + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Joas Schilling + * @author Johannes Riedel + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\Bruteforce; + +use OC\Security\Normalizer\IpAddress; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\Security\Bruteforce\MaxDelayReached; + +/** + * Class Throttler implements the bruteforce protection for security actions in + * Nextcloud. + * + * It is working by logging invalid login attempts to the database and slowing + * down all login attempts from the same subnet. The max delay is 30 seconds and + * the starting delay are 200 milliseconds. (after the first failed login) + * + * This is based on Paragonie's AirBrake for Airship CMS. You can find the original + * code at https://github.com/paragonie/airship/blob/7e5bad7e3c0fbbf324c11f963fd1f80e59762606/src/Engine/Security/AirBrake.php + * + * @package OC\Security\Bruteforce + */ +class Throttler { + public const LOGIN_ACTION = 'login'; + public const MAX_DELAY = 25; + public const MAX_DELAY_MS = 25000; // in milliseconds + public const MAX_ATTEMPTS = 10; + + /** @var IDBConnection */ + private $db; + /** @var ITimeFactory */ + private $timeFactory; + /** @var ILogger */ + private $logger; + /** @var IConfig */ + private $config; + + /** + * @param IDBConnection $db + * @param ITimeFactory $timeFactory + * @param ILogger $logger + * @param IConfig $config + */ + public function __construct(IDBConnection $db, + ITimeFactory $timeFactory, + ILogger $logger, + IConfig $config) { + $this->db = $db; + $this->timeFactory = $timeFactory; + $this->logger = $logger; + $this->config = $config; + } + + /** + * Convert a number of seconds into the appropriate DateInterval + * + * @param int $expire + * @return \DateInterval + */ + private function getCutoff(int $expire): \DateInterval { + $d1 = new \DateTime(); + $d2 = clone $d1; + $d2->sub(new \DateInterval('PT' . $expire . 'S')); + return $d2->diff($d1); + } + + /** + * Calculate the cut off timestamp + * + * @param float $maxAgeHours + * @return int + */ + private function getCutoffTimestamp(float $maxAgeHours = 12.0): int { + return (new \DateTime()) + ->sub($this->getCutoff((int) ($maxAgeHours * 3600))) + ->getTimestamp(); + } + + /** + * Register a failed attempt to bruteforce a security control + * + * @param string $action + * @param string $ip + * @param array $metadata Optional metadata logged to the database + */ + public function registerAttempt(string $action, + string $ip, + array $metadata = []): void { + // No need to log if the bruteforce protection is disabled + if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) { + return; + } + + $ipAddress = new IpAddress($ip); + $values = [ + 'action' => $action, + 'occurred' => $this->timeFactory->getTime(), + 'ip' => (string)$ipAddress, + 'subnet' => $ipAddress->getSubnet(), + 'metadata' => json_encode($metadata), + ]; + + $this->logger->notice( + sprintf( + 'Bruteforce attempt from "%s" detected for action "%s".', + $ip, + $action + ), + [ + 'app' => 'core', + ] + ); + + $qb = $this->db->getQueryBuilder(); + $qb->insert('bruteforce_attempts'); + foreach ($values as $column => $value) { + $qb->setValue($column, $qb->createNamedParameter($value)); + } + $qb->execute(); + } + + /** + * Check if the IP is whitelisted + * + * @param string $ip + * @return bool + */ + private function isIPWhitelisted(string $ip): bool { + if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) { + return true; + } + + $keys = $this->config->getAppKeys('bruteForce'); + $keys = array_filter($keys, function ($key) { + return 0 === strpos($key, 'whitelist_'); + }); + + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $type = 4; + } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $type = 6; + } else { + return false; + } + + $ip = inet_pton($ip); + + foreach ($keys as $key) { + $cidr = $this->config->getAppValue('bruteForce', $key, null); + + $cx = explode('/', $cidr); + $addr = $cx[0]; + $mask = (int)$cx[1]; + + // Do not compare ipv4 to ipv6 + if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) || + ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) { + continue; + } + + $addr = inet_pton($addr); + + $valid = true; + for ($i = 0; $i < $mask; $i++) { + $part = ord($addr[(int)($i/8)]); + $orig = ord($ip[(int)($i/8)]); + + $bitmask = 1 << (7 - ($i % 8)); + + $part = $part & $bitmask; + $orig = $orig & $bitmask; + + if ($part !== $orig) { + $valid = false; + break; + } + } + + if ($valid === true) { + return true; + } + } + + return false; + } + + /** + * Get the throttling delay (in milliseconds) + * + * @param string $ip + * @param string $action optionally filter by action + * @param float $maxAgeHours + * @return int + */ + public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int { + if ($ip === '') { + return 0; + } + + $ipAddress = new IpAddress($ip); + if ($this->isIPWhitelisted((string)$ipAddress)) { + return 0; + } + + $cutoffTime = $this->getCutoffTimestamp($maxAgeHours); + + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->func()->count('*', 'attempts')) + ->from('bruteforce_attempts') + ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime))) + ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet()))); + + if ($action !== '') { + $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action))); + } + + $result = $qb->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + return (int) $row['attempts']; + } + + /** + * Get the throttling delay (in milliseconds) + * + * @param string $ip + * @param string $action optionally filter by action + * @return int + */ + public function getDelay(string $ip, string $action = ''): int { + $attempts = $this->getAttempts($ip, $action); + if ($attempts === 0) { + return 0; + } + + $firstDelay = 0.1; + if ($attempts > self::MAX_ATTEMPTS) { + // Don't ever overflow. Just assume the maxDelay time:s + return self::MAX_DELAY_MS; + } + + $delay = $firstDelay * 2**$attempts; + if ($delay > self::MAX_DELAY) { + return self::MAX_DELAY_MS; + } + return (int) \ceil($delay * 1000); + } + + /** + * Reset the throttling delay for an IP address, action and metadata + * + * @param string $ip + * @param string $action + * @param array $metadata + */ + public function resetDelay(string $ip, string $action, array $metadata): void { + $ipAddress = new IpAddress($ip); + if ($this->isIPWhitelisted((string)$ipAddress)) { + return; + } + + $cutoffTime = $this->getCutoffTimestamp(); + + $qb = $this->db->getQueryBuilder(); + $qb->delete('bruteforce_attempts') + ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime))) + ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet()))) + ->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action))) + ->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata)))); + + $qb->execute(); + } + + /** + * Reset the throttling delay for an IP address + * + * @param string $ip + */ + public function resetDelayForIP($ip) { + $cutoffTime = $this->getCutoffTimestamp(); + + $qb = $this->db->getQueryBuilder(); + $qb->delete('bruteforce_attempts') + ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime))) + ->andWhere($qb->expr()->eq('ip', $qb->createNamedParameter($ip))); + + $qb->execute(); + } + + /** + * Will sleep for the defined amount of time + * + * @param string $ip + * @param string $action optionally filter by action + * @return int the time spent sleeping + */ + public function sleepDelay(string $ip, string $action = ''): int { + $delay = $this->getDelay($ip, $action); + usleep($delay * 1000); + return $delay; + } + + /** + * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes + * In this case a "429 Too Many Request" exception is thrown + * + * @param string $ip + * @param string $action optionally filter by action + * @return int the time spent sleeping + * @throws MaxDelayReached when reached the maximum + */ + public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int { + $delay = $this->getDelay($ip, $action); + if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) { + // If the ip made too many attempts within the last 30 mins we don't execute anymore + throw new MaxDelayReached('Reached maximum delay'); + } + usleep($delay * 1000); + return $delay; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicy.php b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicy.php new file mode 100644 index 0000000..4d41bd5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -0,0 +1,248 @@ + + * @author Roeland Jago Douma + * @author Thomas Citharel + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSP; + +/** + * Class ContentSecurityPolicy extends the public class and adds getter and setters. + * This is necessary since we don't want to expose the setters and getters to the + * public API. + * + * @package OC\Security\CSP + */ +class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy { + /** + * @return boolean + */ + public function isInlineScriptAllowed(): bool { + return $this->inlineScriptAllowed; + } + + /** + * @param boolean $inlineScriptAllowed + */ + public function setInlineScriptAllowed(bool $inlineScriptAllowed) { + $this->inlineScriptAllowed = $inlineScriptAllowed; + } + + /** + * @return boolean + */ + public function isEvalScriptAllowed(): bool { + return $this->evalScriptAllowed; + } + + /** + * @param boolean $evalScriptAllowed + * + * @deprecated 17.0.0 Unsafe eval should not be used anymore. + */ + public function setEvalScriptAllowed(bool $evalScriptAllowed) { + $this->evalScriptAllowed = $evalScriptAllowed; + } + + /** + * @return array + */ + public function getAllowedScriptDomains(): array { + return $this->allowedScriptDomains; + } + + /** + * @param array $allowedScriptDomains + */ + public function setAllowedScriptDomains(array $allowedScriptDomains) { + $this->allowedScriptDomains = $allowedScriptDomains; + } + + /** + * @return boolean + */ + public function isInlineStyleAllowed(): bool { + return $this->inlineStyleAllowed; + } + + /** + * @param boolean $inlineStyleAllowed + */ + public function setInlineStyleAllowed(bool $inlineStyleAllowed) { + $this->inlineStyleAllowed = $inlineStyleAllowed; + } + + /** + * @return array + */ + public function getAllowedStyleDomains(): array { + return $this->allowedStyleDomains; + } + + /** + * @param array $allowedStyleDomains + */ + public function setAllowedStyleDomains(array $allowedStyleDomains) { + $this->allowedStyleDomains = $allowedStyleDomains; + } + + /** + * @return array + */ + public function getAllowedImageDomains(): array { + return $this->allowedImageDomains; + } + + /** + * @param array $allowedImageDomains + */ + public function setAllowedImageDomains(array $allowedImageDomains) { + $this->allowedImageDomains = $allowedImageDomains; + } + + /** + * @return array + */ + public function getAllowedConnectDomains(): array { + return $this->allowedConnectDomains; + } + + /** + * @param array $allowedConnectDomains + */ + public function setAllowedConnectDomains(array $allowedConnectDomains) { + $this->allowedConnectDomains = $allowedConnectDomains; + } + + /** + * @return array + */ + public function getAllowedMediaDomains(): array { + return $this->allowedMediaDomains; + } + + /** + * @param array $allowedMediaDomains + */ + public function setAllowedMediaDomains(array $allowedMediaDomains) { + $this->allowedMediaDomains = $allowedMediaDomains; + } + + /** + * @return array + */ + public function getAllowedObjectDomains(): array { + return $this->allowedObjectDomains; + } + + /** + * @param array $allowedObjectDomains + */ + public function setAllowedObjectDomains(array $allowedObjectDomains) { + $this->allowedObjectDomains = $allowedObjectDomains; + } + + /** + * @return array + */ + public function getAllowedFrameDomains(): array { + return $this->allowedFrameDomains; + } + + /** + * @param array $allowedFrameDomains + */ + public function setAllowedFrameDomains(array $allowedFrameDomains) { + $this->allowedFrameDomains = $allowedFrameDomains; + } + + /** + * @return array + */ + public function getAllowedFontDomains(): array { + return $this->allowedFontDomains; + } + + /** + * @param array $allowedFontDomains + */ + public function setAllowedFontDomains($allowedFontDomains) { + $this->allowedFontDomains = $allowedFontDomains; + } + + /** + * @return array + * @deprecated 15.0.0 use FrameDomains and WorkerSrcDomains + */ + public function getAllowedChildSrcDomains(): array { + return $this->allowedChildSrcDomains; + } + + /** + * @param array $allowedChildSrcDomains + * @deprecated 15.0.0 use FrameDomains and WorkerSrcDomains + */ + public function setAllowedChildSrcDomains($allowedChildSrcDomains) { + $this->allowedChildSrcDomains = $allowedChildSrcDomains; + } + + /** + * @return array + */ + public function getAllowedFrameAncestors(): array { + return $this->allowedFrameAncestors; + } + + /** + * @param array $allowedFrameAncestors + */ + public function setAllowedFrameAncestors($allowedFrameAncestors) { + $this->allowedFrameAncestors = $allowedFrameAncestors; + } + + public function getAllowedWorkerSrcDomains(): array { + return $this->allowedWorkerSrcDomains; + } + + public function setAllowedWorkerSrcDomains(array $allowedWorkerSrcDomains) { + $this->allowedWorkerSrcDomains = $allowedWorkerSrcDomains; + } + + public function getAllowedFormActionDomains(): array { + return $this->allowedFormActionDomains; + } + + public function setAllowedFormActionDomains(array $allowedFormActionDomains): void { + $this->allowedFormActionDomains = $allowedFormActionDomains; + } + + + public function getReportTo(): array { + return $this->reportTo; + } + + public function setReportTo(array $reportTo) { + $this->reportTo = $reportTo; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyManager.php b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyManager.php new file mode 100644 index 0000000..60a176c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyManager.php @@ -0,0 +1,92 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSP; + +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Security\CSP\AddContentSecurityPolicyEvent; +use OCP\Security\IContentSecurityPolicyManager; + +class ContentSecurityPolicyManager implements IContentSecurityPolicyManager { + /** @var ContentSecurityPolicy[] */ + private $policies = []; + + /** @var IEventDispatcher */ + private $dispatcher; + + public function __construct(IEventDispatcher $dispatcher) { + $this->dispatcher = $dispatcher; + } + + /** {@inheritdoc} */ + public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) { + $this->policies[] = $policy; + } + + /** + * Get the configured default policy. This is not in the public namespace + * as it is only supposed to be used by core itself. + * + * @return ContentSecurityPolicy + */ + public function getDefaultPolicy(): ContentSecurityPolicy { + $event = new AddContentSecurityPolicyEvent($this); + $this->dispatcher->dispatchTyped($event); + + $defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy(); + foreach ($this->policies as $policy) { + $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy); + } + return $defaultPolicy; + } + + /** + * Merges the first given policy with the second one + * + * @param ContentSecurityPolicy $defaultPolicy + * @param EmptyContentSecurityPolicy $originalPolicy + * @return ContentSecurityPolicy + */ + public function mergePolicies(ContentSecurityPolicy $defaultPolicy, + EmptyContentSecurityPolicy $originalPolicy): ContentSecurityPolicy { + foreach ((object)(array)$originalPolicy as $name => $value) { + $setter = 'set'.ucfirst($name); + if (\is_array($value)) { + $getter = 'get'.ucfirst($name); + $currentValues = \is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : []; + $defaultPolicy->$setter(array_values(array_unique(array_merge($currentValues, $value)))); + } elseif (\is_bool($value)) { + $defaultPolicy->$setter($value); + } + } + + return $defaultPolicy; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php new file mode 100644 index 0000000..cc5a3c2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -0,0 +1,96 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Pavel Krasikov + * @author Roeland Jago Douma + * @author Sam Bull + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\CSP; + +use OC\AppFramework\Http\Request; +use OC\Security\CSRF\CsrfTokenManager; +use OCP\IRequest; + +/** + * @package OC\Security\CSP + */ +class ContentSecurityPolicyNonceManager { + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var IRequest */ + private $request; + /** @var string */ + private $nonce = ''; + + /** + * @param CsrfTokenManager $csrfTokenManager + * @param IRequest $request + */ + public function __construct(CsrfTokenManager $csrfTokenManager, + IRequest $request) { + $this->csrfTokenManager = $csrfTokenManager; + $this->request = $request; + } + + /** + * Returns the current CSP nounce + * + * @return string + */ + public function getNonce(): string { + if ($this->nonce === '') { + if (empty($this->request->server['CSP_NONCE'])) { + $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue()); + } else { + $this->nonce = $this->request->server['CSP_NONCE']; + } + } + + return $this->nonce; + } + + /** + * Check if the browser supports CSP v3 + * + * @return bool + */ + public function browserSupportsCspV3(): bool { + $browserWhitelist = [ + Request::USER_AGENT_CHROME, + // Firefox 45+ + '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', + // Safari 12+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/(?:1[2-9]|[2-9][0-9])\.[0-9]+(?:\.[0-9]+)? Safari\/[0-9.A-Z]+$/', + ]; + + if ($this->request->isUserAgent($browserWhitelist)) { + return true; + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfToken.php b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfToken.php new file mode 100644 index 0000000..25ec857 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfToken.php @@ -0,0 +1,82 @@ + + * @author Leon Klingele + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSRF; + +/** + * Class CsrfToken represents the stored or provided CSRF token. To mitigate + * BREACH alike vulnerabilities the token is returned in an encrypted value as + * well in an unencrypted value. For display measures to the user always the + * unencrypted one should be chosen. + * + * @package OC\Security\CSRF + */ +class CsrfToken { + /** @var string */ + private $value; + /** @var string */ + private $encryptedValue = ''; + + /** + * @param string $value Value of the token. Can be encrypted or not encrypted. + */ + public function __construct(string $value) { + $this->value = $value; + } + + /** + * Encrypted value of the token. This is used to mitigate BREACH alike + * vulnerabilities. For display measures do use this functionality. + * + * @return string + */ + public function getEncryptedValue(): string { + if ($this->encryptedValue === '') { + $sharedSecret = random_bytes(\strlen($this->value)); + $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . base64_encode($sharedSecret); + } + + return $this->encryptedValue; + } + + /** + * The unencrypted value of the token. Used for decrypting an already + * encrypted token. + * + * @return string + */ + public function getDecryptedValue(): string { + $token = explode(':', $this->value); + if (\count($token) !== 2) { + return ''; + } + $obfuscatedToken = $token[0]; + $secret = $token[1]; + return base64_decode($obfuscatedToken) ^ base64_decode($secret); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenGenerator.php b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenGenerator.php new file mode 100644 index 0000000..721c427 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenGenerator.php @@ -0,0 +1,57 @@ + + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSRF; + +use OCP\Security\ISecureRandom; + +/** + * Class CsrfTokenGenerator is used to generate a cryptographically secure + * pseudo-random number for the token. + * + * @package OC\Security\CSRF + */ +class CsrfTokenGenerator { + /** @var ISecureRandom */ + private $random; + + /** + * @param ISecureRandom $random + */ + public function __construct(ISecureRandom $random) { + $this->random = $random; + } + + /** + * Generate a new CSRF token. + * + * @param int $length Length of the token in characters. + * @return string + */ + public function generateToken(int $length = 32): string { + return $this->random->generate($length); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenManager.php b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenManager.php new file mode 100644 index 0000000..f0536c7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/CsrfTokenManager.php @@ -0,0 +1,112 @@ + + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSRF; + +use OC\Security\CSRF\TokenStorage\SessionStorage; + +/** + * Class CsrfTokenManager is the manager for all CSRF token related activities. + * + * @package OC\Security\CSRF + */ +class CsrfTokenManager { + /** @var CsrfTokenGenerator */ + private $tokenGenerator; + /** @var SessionStorage */ + private $sessionStorage; + /** @var CsrfToken|null */ + private $csrfToken = null; + + /** + * @param CsrfTokenGenerator $tokenGenerator + * @param SessionStorage $storageInterface + */ + public function __construct(CsrfTokenGenerator $tokenGenerator, + SessionStorage $storageInterface) { + $this->tokenGenerator = $tokenGenerator; + $this->sessionStorage = $storageInterface; + } + + /** + * Returns the current CSRF token, if none set it will create a new one. + * + * @return CsrfToken + */ + public function getToken(): CsrfToken { + if (!\is_null($this->csrfToken)) { + return $this->csrfToken; + } + + if ($this->sessionStorage->hasToken()) { + $value = $this->sessionStorage->getToken(); + } else { + $value = $this->tokenGenerator->generateToken(); + $this->sessionStorage->setToken($value); + } + + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; + } + + /** + * Invalidates any current token and sets a new one. + * + * @return CsrfToken + */ + public function refreshToken(): CsrfToken { + $value = $this->tokenGenerator->generateToken(); + $this->sessionStorage->setToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; + } + + /** + * Remove the current token from the storage. + */ + public function removeToken() { + $this->csrfToken = null; + $this->sessionStorage->removeToken(); + } + + /** + * Verifies whether the provided token is valid. + * + * @param CsrfToken $token + * @return bool + */ + public function isTokenValid(CsrfToken $token): bool { + if (!$this->sessionStorage->hasToken()) { + return false; + } + + return hash_equals( + $this->sessionStorage->getToken(), + $token->getDecryptedValue() + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CSRF/TokenStorage/SessionStorage.php b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/TokenStorage/SessionStorage.php new file mode 100644 index 0000000..ed9b068 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CSRF/TokenStorage/SessionStorage.php @@ -0,0 +1,94 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security\CSRF\TokenStorage; + +use OCP\ISession; + +/** + * Class SessionStorage provides the session storage + * + * @package OC\Security\CSRF\TokenStorage + */ +class SessionStorage { + /** @var ISession */ + private $session; + + /** + * @param ISession $session + */ + public function __construct(ISession $session) { + $this->session = $session; + } + + /** + * @param ISession $session + */ + public function setSession(ISession $session) { + $this->session = $session; + } + + /** + * Returns the current token or throws an exception if none is found. + * + * @return string + * @throws \Exception + */ + public function getToken(): string { + $token = $this->session->get('requesttoken'); + if (empty($token)) { + throw new \Exception('Session does not contain a requesttoken'); + } + + return $token; + } + + /** + * Set the valid current token to $value. + * + * @param string $value + */ + public function setToken(string $value) { + $this->session->set('requesttoken', $value); + } + + /** + * Removes the current token. + */ + public function removeToken() { + $this->session->remove('requesttoken'); + } + /** + * Whether the storage has a storage. + * + * @return bool + */ + public function hasToken(): bool { + return $this->session->exists('requesttoken'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Certificate.php b/docker/overlays/nextcloud/html/lib/private/Security/Certificate.php new file mode 100644 index 0000000..c89122f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Certificate.php @@ -0,0 +1,131 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OCP\ICertificate; + +class Certificate implements ICertificate { + protected $name; + + protected $commonName; + + protected $organization; + + protected $serial; + + protected $issueDate; + + protected $expireDate; + + protected $issuerName; + + protected $issuerOrganization; + + /** + * @param string $data base64 encoded certificate + * @param string $name + * @throws \Exception If the certificate could not get parsed + */ + public function __construct($data, $name) { + $this->name = $name; + $gmt = new \DateTimeZone('GMT'); + + // If string starts with "file://" ignore the certificate + $query = 'file://'; + if (strtolower(substr($data, 0, strlen($query))) === $query) { + throw new \Exception('Certificate could not get parsed.'); + } + + $info = openssl_x509_parse($data); + if (!is_array($info)) { + throw new \Exception('Certificate could not get parsed.'); + } + + $this->commonName = isset($info['subject']['CN']) ? $info['subject']['CN'] : null; + $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; + $this->issueDate = new \DateTime('@' . $info['validFrom_time_t'], $gmt); + $this->expireDate = new \DateTime('@' . $info['validTo_time_t'], $gmt); + $this->issuerName = isset($info['issuer']['CN']) ? $info['issuer']['CN'] : null; + $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @return string|null + */ + public function getCommonName() { + return $this->commonName; + } + + /** + * @return string + */ + public function getOrganization() { + return $this->organization; + } + + /** + * @return \DateTime + */ + public function getIssueDate() { + return $this->issueDate; + } + + /** + * @return \DateTime + */ + public function getExpireDate() { + return $this->expireDate; + } + + /** + * @return bool + */ + public function isExpired() { + $now = new \DateTime(); + return $this->issueDate > $now or $now > $this->expireDate; + } + + /** + * @return string|null + */ + public function getIssuerName() { + return $this->issuerName; + } + + /** + * @return string|null + */ + public function getIssuerOrganization() { + return $this->issuerOrganization; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CertificateManager.php b/docker/overlays/nextcloud/html/lib/private/Security/CertificateManager.php new file mode 100644 index 0000000..e69132f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CertificateManager.php @@ -0,0 +1,288 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OC\Files\Filesystem; +use OCP\ICertificateManager; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Security\ISecureRandom; + +/** + * Manage trusted certificates for users + */ +class CertificateManager implements ICertificateManager { + /** + * @var string + */ + protected $uid; + + /** + * @var \OC\Files\View + */ + protected $view; + + /** + * @var IConfig + */ + protected $config; + + /** + * @var ILogger + */ + protected $logger; + + /** @var ISecureRandom */ + protected $random; + + /** + * @param string $uid + * @param \OC\Files\View $view relative to data/ + * @param IConfig $config + * @param ILogger $logger + * @param ISecureRandom $random + */ + public function __construct($uid, + \OC\Files\View $view, + IConfig $config, + ILogger $logger, + ISecureRandom $random) { + $this->uid = $uid; + $this->view = $view; + $this->config = $config; + $this->logger = $logger; + $this->random = $random; + } + + /** + * Returns all certificates trusted by the user + * + * @return \OCP\ICertificate[] + */ + public function listCertificates() { + if (!$this->config->getSystemValue('installed', false)) { + return []; + } + + $path = $this->getPathToCertificates() . 'uploads/'; + if (!$this->view->is_dir($path)) { + return []; + } + $result = []; + $handle = $this->view->opendir($path); + if (!is_resource($handle)) { + return []; + } + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..') { + try { + $result[] = new Certificate($this->view->file_get_contents($path . $file), $file); + } catch (\Exception $e) { + } + } + } + closedir($handle); + return $result; + } + + /** + * create the certificate bundle of all trusted certificated + */ + public function createCertificateBundle() { + $path = $this->getPathToCertificates(); + $certs = $this->listCertificates(); + + if (!$this->view->file_exists($path)) { + $this->view->mkdir($path); + } + + $defaultCertificates = file_get_contents(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt'); + if (strlen($defaultCertificates) < 1024) { // sanity check to verify that we have some content for our bundle + // log as exception so we have a stacktrace + $this->logger->logException(new \Exception('Shipped ca-bundle is empty, refusing to create certificate bundle')); + return; + } + + $certPath = $path . 'rootcerts.crt'; + $tmpPath = $certPath . '.tmp' . $this->random->generate(10, ISecureRandom::CHAR_DIGITS); + $fhCerts = $this->view->fopen($tmpPath, 'w'); + + // Write user certificates + foreach ($certs as $cert) { + $file = $path . '/uploads/' . $cert->getName(); + $data = $this->view->file_get_contents($file); + if (strpos($data, 'BEGIN CERTIFICATE')) { + fwrite($fhCerts, $data); + fwrite($fhCerts, "\r\n"); + } + } + + // Append the default certificates + fwrite($fhCerts, $defaultCertificates); + + // Append the system certificate bundle + $systemBundle = $this->getCertificateBundle(null); + if ($systemBundle !== $certPath && $this->view->file_exists($systemBundle)) { + $systemCertificates = $this->view->file_get_contents($systemBundle); + fwrite($fhCerts, $systemCertificates); + } + + fclose($fhCerts); + + $this->view->rename($tmpPath, $certPath); + } + + /** + * Save the certificate and re-generate the certificate bundle + * + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @return \OCP\ICertificate + * @throws \Exception If the certificate could not get added + */ + public function addCertificate($certificate, $name) { + if (!Filesystem::isValidPath($name) or Filesystem::isFileBlacklisted($name)) { + throw new \Exception('Filename is not valid'); + } + + $dir = $this->getPathToCertificates() . 'uploads/'; + if (!$this->view->file_exists($dir)) { + $this->view->mkdir($dir); + } + + try { + $file = $dir . $name; + $certificateObject = new Certificate($certificate, $name); + $this->view->file_put_contents($file, $certificate); + $this->createCertificateBundle(); + return $certificateObject; + } catch (\Exception $e) { + throw $e; + } + } + + /** + * Remove the certificate and re-generate the certificate bundle + * + * @param string $name + * @return bool + */ + public function removeCertificate($name) { + if (!Filesystem::isValidPath($name)) { + return false; + } + $path = $this->getPathToCertificates() . 'uploads/'; + if ($this->view->file_exists($path . $name)) { + $this->view->unlink($path . $name); + $this->createCertificateBundle(); + } + return true; + } + + /** + * Get the path to the certificate bundle for this user + * + * @param string|null $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle + * @return string + */ + public function getCertificateBundle($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + return $this->getPathToCertificates($uid) . 'rootcerts.crt'; + } + + /** + * Get the full local path to the certificate bundle for this user + * + * @param string $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle + * @return string + */ + public function getAbsoluteBundlePath($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + if ($this->needsRebundling($uid)) { + if (is_null($uid)) { + $manager = new CertificateManager(null, $this->view, $this->config, $this->logger, $this->random); + $manager->createCertificateBundle(); + } else { + $this->createCertificateBundle(); + } + } + return $this->view->getLocalFile($this->getCertificateBundle($uid)); + } + + /** + * @param string|null $uid (optional) user to get the certificate path for, use `null` to get the system path + * @return string + */ + private function getPathToCertificates($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + return is_null($uid) ? '/files_external/' : '/' . $uid . '/files_external/'; + } + + /** + * Check if we need to re-bundle the certificates because one of the sources has updated + * + * @param string $uid (optional) user to get the certificate path for, use `null` to get the system path + * @return bool + */ + private function needsRebundling($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + $sourceMTimes = [$this->getFilemtimeOfCaBundle()]; + $targetBundle = $this->getCertificateBundle($uid); + if (!$this->view->file_exists($targetBundle)) { + return true; + } + + if (!is_null($uid)) { // also depend on the system bundle + $sourceMTimes[] = $this->view->filemtime($this->getCertificateBundle(null)); + } + + $sourceMTime = array_reduce($sourceMTimes, function ($max, $mtime) { + return max($max, $mtime); + }, 0); + return $sourceMTime > $this->view->filemtime($targetBundle); + } + + /** + * get mtime of ca-bundle shipped by Nextcloud + * + * @return int + */ + protected function getFilemtimeOfCaBundle() { + return filemtime(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/CredentialsManager.php b/docker/overlays/nextcloud/html/lib/private/Security/CredentialsManager.php new file mode 100644 index 0000000..ace8e68 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/CredentialsManager.php @@ -0,0 +1,126 @@ + + * @author Christoph Wurst + * @author Robin McCorkell + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OCP\IDBConnection; +use OCP\Security\ICredentialsManager; +use OCP\Security\ICrypto; + +/** + * Store and retrieve credentials for external services + * + * @package OC\Security + */ +class CredentialsManager implements ICredentialsManager { + public const DB_TABLE = 'credentials'; + + /** @var ICrypto */ + protected $crypto; + + /** @var IDBConnection */ + protected $dbConnection; + + /** + * @param ICrypto $crypto + * @param IDBConnection $dbConnection + */ + public function __construct(ICrypto $crypto, IDBConnection $dbConnection) { + $this->crypto = $crypto; + $this->dbConnection = $dbConnection; + } + + /** + * Store a set of credentials + * + * @param string $userId empty string for system-wide credentials + * @param string $identifier + * @param mixed $credentials + */ + public function store($userId, $identifier, $credentials) { + $value = $this->crypto->encrypt(json_encode($credentials)); + + $this->dbConnection->setValues(self::DB_TABLE, [ + 'user' => (string)$userId, + 'identifier' => $identifier, + ], [ + 'credentials' => $value, + ]); + } + + /** + * Retrieve a set of credentials + * + * @param string $userId empty string for system-wide credentials + * @param string $identifier + * @return mixed + */ + public function retrieve($userId, $identifier) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('credentials') + ->from(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))) + ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) + ; + $result = $qb->execute()->fetch(); + + if (!$result) { + return null; + } + $value = $result['credentials']; + + return json_decode($this->crypto->decrypt($value), true); + } + + /** + * Delete a set of credentials + * + * @param string $userId empty string for system-wide credentials + * @param string $identifier + * @return int rows removed + */ + public function delete($userId, $identifier) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))) + ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) + ; + return $qb->execute(); + } + + /** + * Erase all credentials stored for a user + * + * @param string $userId + * @return int rows removed + */ + public function erase($userId) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter($userId))) + ; + return $qb->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Crypto.php b/docker/overlays/nextcloud/html/lib/private/Security/Crypto.php new file mode 100644 index 0000000..1544482 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Crypto.php @@ -0,0 +1,149 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OCP\IConfig; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; +use phpseclib\Crypt\AES; +use phpseclib\Crypt\Hash; + +/** + * Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided + * it will use the secret defined in config.php as key. Additionally the message will be HMAC'd. + * + * Usage: + * $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText'); + * $encryptWithCustompassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password'); + * + * @package OC\Security + */ +class Crypto implements ICrypto { + /** @var AES $cipher */ + private $cipher; + /** @var int */ + private $ivLength = 16; + /** @var IConfig */ + private $config; + + /** + * @param IConfig $config + * @param ISecureRandom $random + */ + public function __construct(IConfig $config) { + $this->cipher = new AES(); + $this->config = $config; + } + + /** + * @param string $message The message to authenticate + * @param string $password Password to use (defaults to `secret` in config.php) + * @return string Calculated HMAC + */ + public function calculateHMAC(string $message, string $password = ''): string { + if ($password === '') { + $password = $this->config->getSystemValue('secret'); + } + + // Append an "a" behind the password and hash it to prevent reusing the same password as for encryption + $password = hash('sha512', $password . 'a'); + + $hash = new Hash('sha512'); + $hash->setKey($password); + return $hash->hash($message); + } + + /** + * Encrypts a value and adds an HMAC (Encrypt-Then-MAC) + * @param string $plaintext + * @param string $password Password to encrypt, if not specified the secret from config.php will be taken + * @return string Authenticated ciphertext + */ + public function encrypt(string $plaintext, string $password = ''): string { + if ($password === '') { + $password = $this->config->getSystemValue('secret'); + } + $this->cipher->setPassword($password); + + $iv = \random_bytes($this->ivLength); + $this->cipher->setIV($iv); + + $ciphertext = bin2hex($this->cipher->encrypt($plaintext)); + $iv = bin2hex($iv); + $hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password)); + + return $ciphertext.'|'.$iv.'|'.$hmac.'|2'; + } + + /** + * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac) + * @param string $authenticatedCiphertext + * @param string $password Password to encrypt, if not specified the secret from config.php will be taken + * @return string plaintext + * @throws \Exception If the HMAC does not match + * @throws \Exception If the decryption failed + */ + public function decrypt(string $authenticatedCiphertext, string $password = ''): string { + if ($password === '') { + $password = $this->config->getSystemValue('secret'); + } + $this->cipher->setPassword($password); + + $parts = explode('|', $authenticatedCiphertext); + $partCount = \count($parts); + if ($partCount < 3 || $partCount > 4) { + throw new \Exception('Authenticated ciphertext could not be decoded.'); + } + + $ciphertext = hex2bin($parts[0]); + $iv = $parts[1]; + $hmac = hex2bin($parts[2]); + + if ($partCount === 4) { + $version = $parts[3]; + if ($version === '2') { + $iv = hex2bin($iv); + } + } + + $this->cipher->setIV($iv); + + if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $password), $hmac)) { + throw new \Exception('HMAC does not match.'); + } + + $result = $this->cipher->decrypt($ciphertext); + if ($result === false) { + throw new \Exception('Decryption failed'); + } + + return $result; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicy.php b/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicy.php new file mode 100644 index 0000000..9355670 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicy.php @@ -0,0 +1,77 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\FeaturePolicy; + +class FeaturePolicy extends \OCP\AppFramework\Http\FeaturePolicy { + public function getAutoplayDomains(): array { + return $this->autoplayDomains; + } + + public function setAutoplayDomains(array $autoplayDomains): void { + $this->autoplayDomains = $autoplayDomains; + } + + public function getCameraDomains(): array { + return $this->cameraDomains; + } + + public function setCameraDomains(array $cameraDomains): void { + $this->cameraDomains = $cameraDomains; + } + + public function getFullscreenDomains(): array { + return $this->fullscreenDomains; + } + + public function setFullscreenDomains(array $fullscreenDomains): void { + $this->fullscreenDomains = $fullscreenDomains; + } + + public function getGeolocationDomains(): array { + return $this->geolocationDomains; + } + + public function setGeolocationDomains(array $geolocationDomains): void { + $this->geolocationDomains = $geolocationDomains; + } + + public function getMicrophoneDomains(): array { + return $this->microphoneDomains; + } + + public function setMicrophoneDomains(array $microphoneDomains): void { + $this->microphoneDomains = $microphoneDomains; + } + + public function getPaymentDomains(): array { + return $this->paymentDomains; + } + + public function setPaymentDomains(array $paymentDomains): void { + $this->paymentDomains = $paymentDomains; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php b/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php new file mode 100644 index 0000000..b2959c3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php @@ -0,0 +1,79 @@ + + * + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\FeaturePolicy; + +use OCP\AppFramework\Http\EmptyFeaturePolicy; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Security\FeaturePolicy\AddFeaturePolicyEvent; + +class FeaturePolicyManager { + /** @var EmptyFeaturePolicy[] */ + private $policies = []; + + /** @var IEventDispatcher */ + private $dispatcher; + + public function __construct(IEventDispatcher $dispatcher) { + $this->dispatcher = $dispatcher; + } + + public function addDefaultPolicy(EmptyFeaturePolicy $policy): void { + $this->policies[] = $policy; + } + + public function getDefaultPolicy(): FeaturePolicy { + $event = new AddFeaturePolicyEvent($this); + $this->dispatcher->dispatchTyped($event); + + $defaultPolicy = new FeaturePolicy(); + foreach ($this->policies as $policy) { + $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy); + } + return $defaultPolicy; + } + + /** + * Merges the first given policy with the second one + * + */ + public function mergePolicies(FeaturePolicy $defaultPolicy, + EmptyFeaturePolicy $originalPolicy): FeaturePolicy { + foreach ((object)(array)$originalPolicy as $name => $value) { + $setter = 'set' . ucfirst($name); + if (\is_array($value)) { + $getter = 'get' . ucfirst($name); + $currentValues = \is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : []; + $defaultPolicy->$setter(\array_values(\array_unique(\array_merge($currentValues, $value)))); + } elseif (\is_bool($value)) { + $defaultPolicy->$setter($value); + } + } + + return $defaultPolicy; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Hasher.php b/docker/overlays/nextcloud/html/lib/private/Security/Hasher.php new file mode 100644 index 0000000..4b068ce --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Hasher.php @@ -0,0 +1,211 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author MichaIng + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OCP\IConfig; +use OCP\Security\IHasher; + +/** + * Class Hasher provides some basic hashing functions. Furthermore, it supports legacy hashes + * used by previous versions of ownCloud and helps migrating those hashes to newer ones. + * + * The hashes generated by this class are prefixed (version|hash) with a version parameter to allow possible + * updates in the future. + * Possible versions: + * - 1 (Initial version) + * + * Usage: + * // Hashing a message + * $hash = \OC::$server->getHasher()->hash('MessageToHash'); + * // Verifying a message - $newHash will contain the newly calculated hash + * $newHash = null; + * var_dump(\OC::$server->getHasher()->verify('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', $newHash)); + * var_dump($newHash); + * + * @package OC\Security + */ +class Hasher implements IHasher { + /** @var IConfig */ + private $config; + /** @var array Options passed to password_hash and password_needs_rehash */ + private $options = []; + /** @var string Salt used for legacy passwords */ + private $legacySalt = null; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + + if (\defined('PASSWORD_ARGON2ID') || \defined('PASSWORD_ARGON2I')) { + // password_hash fails, when the minimum values are undershot. + // In this case, apply minimum. + $this->options['threads'] = max($this->config->getSystemValueInt('hashingThreads', PASSWORD_ARGON2_DEFAULT_THREADS), 1); + // The minimum memory cost is 8 KiB per thread. + $this->options['memory_cost'] = max($this->config->getSystemValueInt('hashingMemoryCost', PASSWORD_ARGON2_DEFAULT_MEMORY_COST), $this->options['threads'] * 8); + $this->options['time_cost'] = max($this->config->getSystemValueInt('hashingTimeCost', PASSWORD_ARGON2_DEFAULT_TIME_COST), 1); + } + + $hashingCost = $this->config->getSystemValue('hashingCost', null); + if (!\is_null($hashingCost)) { + $this->options['cost'] = $hashingCost; + } + } + + /** + * Hashes a message using PHP's `password_hash` functionality. + * Please note that the size of the returned string is not guaranteed + * and can be up to 255 characters. + * + * @param string $message Message to generate hash from + * @return string Hash of the message with appended version parameter + */ + public function hash(string $message): string { + $alg = $this->getPrefferedAlgorithm(); + + if (\defined('PASSWORD_ARGON2ID') && $alg === PASSWORD_ARGON2ID) { + return 3 . '|' . password_hash($message, PASSWORD_ARGON2ID, $this->options); + } + + if (\defined('PASSWORD_ARGON2I') && $alg === PASSWORD_ARGON2I) { + return 2 . '|' . password_hash($message, PASSWORD_ARGON2I, $this->options); + } + + return 1 . '|' . password_hash($message, PASSWORD_BCRYPT, $this->options); + } + + /** + * Get the version and hash from a prefixedHash + * @param string $prefixedHash + * @return null|array Null if the hash is not prefixed, otherwise array('version' => 1, 'hash' => 'foo') + */ + protected function splitHash(string $prefixedHash) { + $explodedString = explode('|', $prefixedHash, 2); + if (\count($explodedString) === 2) { + if ((int)$explodedString[0] > 0) { + return ['version' => (int)$explodedString[0], 'hash' => $explodedString[1]]; + } + } + + return null; + } + + /** + * Verify legacy hashes + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash + * @return bool Whether $hash is a valid hash of $message + */ + protected function legacyHashVerify($message, $hash, &$newHash = null): bool { + if (empty($this->legacySalt)) { + $this->legacySalt = $this->config->getSystemValue('passwordsalt', ''); + } + + // Verify whether it matches a legacy PHPass or SHA1 string + $hashLength = \strlen($hash); + if (($hashLength === 60 && password_verify($message.$this->legacySalt, $hash)) || + ($hashLength === 40 && hash_equals($hash, sha1($message)))) { + $newHash = $this->hash($message); + return true; + } + + return false; + } + + /** + * Verify V1 (blowfish) hashes + * Verify V2 (argon2i) hashes + * Verify V3 (argon2id) hashes + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one. + * @return bool Whether $hash is a valid hash of $message + */ + protected function verifyHash(string $message, string $hash, &$newHash = null): bool { + if (password_verify($message, $hash)) { + if ($this->needsRehash($hash)) { + $newHash = $this->hash($message); + } + return true; + } + + return false; + } + + /** + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one. + * @return bool Whether $hash is a valid hash of $message + */ + public function verify(string $message, string $hash, &$newHash = null): bool { + $splittedHash = $this->splitHash($hash); + + if (isset($splittedHash['version'])) { + switch ($splittedHash['version']) { + case 3: + case 2: + case 1: + return $this->verifyHash($message, $splittedHash['hash'], $newHash); + } + } else { + return $this->legacyHashVerify($message, $hash, $newHash); + } + + return false; + } + + private function needsRehash(string $hash): bool { + $algorithm = $this->getPrefferedAlgorithm(); + + return password_needs_rehash($hash, $algorithm, $this->options); + } + + private function getPrefferedAlgorithm() { + $default = PASSWORD_BCRYPT; + if (\defined('PASSWORD_ARGON2I')) { + $default = PASSWORD_ARGON2I; + } + + if (\defined('PASSWORD_ARGON2ID')) { + $default = PASSWORD_ARGON2ID; + } + + // Check if we should use PASSWORD_DEFAULT + if ($this->config->getSystemValue('hashing_default_password', false) === true) { + $default = PASSWORD_DEFAULT; + } + + return $default; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Key.php b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Key.php new file mode 100644 index 0000000..342d44b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Key.php @@ -0,0 +1,52 @@ + + * + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +class Key { + /** @var string */ + private $publicKey; + /** @var string */ + private $privateKey; + + /** + * @param string $publicKey + * @param string $privateKey + */ + public function __construct(string $publicKey, string $privateKey) { + $this->publicKey = $publicKey; + $this->privateKey = $privateKey; + } + + public function getPrivate(): string { + return $this->privateKey; + } + + public function getPublic(): string { + return $this->publicKey; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Manager.php b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Manager.php new file mode 100644 index 0000000..2fa09da --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Manager.php @@ -0,0 +1,172 @@ + + * + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +use OC\Files\AppData\Factory; +use OCP\Files\IAppData; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use OCP\Security\ICrypto; + +class Manager { + /** @var IAppData */ + private $appData; + /** @var ICrypto */ + private $crypto; + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + + public function __construct(Factory $appDataFactory, + ICrypto $crypto, + IConfig $config, + ILogger $logger + ) { + $this->appData = $appDataFactory->get('identityproof'); + $this->crypto = $crypto; + $this->config = $config; + $this->logger = $logger; + } + + /** + * Calls the openssl functions to generate a public and private key. + * In a separate function for unit testing purposes. + * + * @return array [$publicKey, $privateKey] + * @throws \RuntimeException + */ + protected function generateKeyPair(): array { + $config = [ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + ]; + + // Generate new key + $res = openssl_pkey_new($config); + + if ($res === false) { + $this->logOpensslError(); + throw new \RuntimeException('OpenSSL reported a problem'); + } + + if (openssl_pkey_export($res, $privateKey, null, $config) === false) { + $this->logOpensslError(); + throw new \RuntimeException('OpenSSL reported a problem'); + } + + // Extract the public key from $res to $pubKey + $publicKey = openssl_pkey_get_details($res); + $publicKey = $publicKey['key']; + + return [$publicKey, $privateKey]; + } + + /** + * Generate a key for a given ID + * Note: If a key already exists it will be overwritten + * + * @param string $id key id + * @return Key + * @throws \RuntimeException + */ + protected function generateKey(string $id): Key { + list($publicKey, $privateKey) = $this->generateKeyPair(); + + // Write the private and public key to the disk + try { + $this->appData->newFolder($id); + } catch (\Exception $e) { + } + $folder = $this->appData->getFolder($id); + $folder->newFile('private') + ->putContent($this->crypto->encrypt($privateKey)); + $folder->newFile('public') + ->putContent($publicKey); + + return new Key($publicKey, $privateKey); + } + + /** + * Get key for a specific id + * + * @param string $id + * @return Key + * @throws \RuntimeException + */ + protected function retrieveKey(string $id): Key { + try { + $folder = $this->appData->getFolder($id); + $privateKey = $this->crypto->decrypt( + $folder->getFile('private')->getContent() + ); + $publicKey = $folder->getFile('public')->getContent(); + return new Key($publicKey, $privateKey); + } catch (\Exception $e) { + return $this->generateKey($id); + } + } + + /** + * Get public and private key for $user + * + * @param IUser $user + * @return Key + * @throws \RuntimeException + */ + public function getKey(IUser $user): Key { + $uid = $user->getUID(); + return $this->retrieveKey('user-' . $uid); + } + + /** + * Get instance wide public and private key + * + * @return Key + * @throws \RuntimeException + */ + public function getSystemKey(): Key { + $instanceId = $this->config->getSystemValue('instanceid', null); + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + return $this->retrieveKey('system-' . $instanceId); + } + + private function logOpensslError(): void { + $errors = []; + while ($error = openssl_error_string()) { + $errors[] = $error; + } + $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Signer.php b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Signer.php new file mode 100644 index 0000000..26293a5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/IdentityProof/Signer.php @@ -0,0 +1,108 @@ + + * + * @author Christoph Wurst + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IUser; +use OCP\IUserManager; + +class Signer { + /** @var Manager */ + private $keyManager; + /** @var ITimeFactory */ + private $timeFactory; + /** @var IUserManager */ + private $userManager; + + /** + * @param Manager $keyManager + * @param ITimeFactory $timeFactory + * @param IUserManager $userManager + */ + public function __construct(Manager $keyManager, + ITimeFactory $timeFactory, + IUserManager $userManager) { + $this->keyManager = $keyManager; + $this->timeFactory = $timeFactory; + $this->userManager = $userManager; + } + + /** + * Returns a signed blob for $data + * + * @param string $type + * @param array $data + * @param IUser $user + * @return array ['message', 'signature'] + */ + public function sign(string $type, array $data, IUser $user): array { + $privateKey = $this->keyManager->getKey($user)->getPrivate(); + $data = [ + 'data' => $data, + 'type' => $type, + 'signer' => $user->getCloudId(), + 'timestamp' => $this->timeFactory->getTime(), + ]; + openssl_sign(json_encode($data), $signature, $privateKey, OPENSSL_ALGO_SHA512); + + return [ + 'message' => $data, + 'signature' => base64_encode($signature), + ]; + } + + /** + * Whether the data is signed properly + * + * @param array $data + * @return bool + */ + public function verify(array $data): bool { + if (isset($data['message']) + && isset($data['signature']) + && isset($data['message']['signer']) + ) { + $location = strrpos($data['message']['signer'], '@'); + $userId = substr($data['message']['signer'], 0, $location); + + $user = $this->userManager->get($userId); + if ($user !== null) { + $key = $this->keyManager->getKey($user); + return (bool)openssl_verify( + json_encode($data['message']), + base64_decode($data['signature']), + $key->getPublic(), + OPENSSL_ALGO_SHA512 + ); + } + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/Normalizer/IpAddress.php b/docker/overlays/nextcloud/html/lib/private/Security/Normalizer/IpAddress.php new file mode 100644 index 0000000..b471c49 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/Normalizer/IpAddress.php @@ -0,0 +1,120 @@ + + * + * @author Konrad Bucheli + * @author Lukas Reschke + * @author Roeland Jago Douma + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\Normalizer; + +/** + * Class IpAddress is used for normalizing IPv4 and IPv6 addresses in security + * relevant contexts in Nextcloud. + * + * @package OC\Security\Normalizer + */ +class IpAddress { + /** @var string */ + private $ip; + + /** + * @param string $ip IP to normalized + */ + public function __construct(string $ip) { + $this->ip = $ip; + } + + /** + * Return the given subnet for an IPv4 address and mask bits + * + * @param string $ip + * @param int $maskBits + * @return string + */ + private function getIPv4Subnet(string $ip, int $maskBits = 32): string { + $binary = \inet_pton($ip); + for ($i = 32; $i > $maskBits; $i -= 8) { + $j = \intdiv($i, 8) - 1; + $k = (int) \min(8, $i - $maskBits); + $mask = (0xff - ((2 ** $k) - 1)); + $int = \unpack('C', $binary[$j]); + $binary[$j] = \pack('C', $int[1] & $mask); + } + return \inet_ntop($binary).'/'.$maskBits; + } + + /** + * Return the given subnet for an IPv6 address and mask bits + * + * @param string $ip + * @param int $maskBits + * @return string + */ + private function getIPv6Subnet(string $ip, int $maskBits = 48): string { + if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1] + $ip = substr($ip, 1, strlen($ip) - 2); + } + $pos = strpos($ip, '%'); // if there is an explicit interface added to the IP, e.g. fe80::ae2d:d1e7:fe1e:9a8d%enp2s0 + if ($pos !== false) { + $ip = substr($ip, 0, $pos-1); + } + $binary = \inet_pton($ip); + for ($i = 128; $i > $maskBits; $i -= 8) { + $j = \intdiv($i, 8) - 1; + $k = (int) \min(8, $i - $maskBits); + $mask = (0xff - ((2 ** $k) - 1)); + $int = \unpack('C', $binary[$j]); + $binary[$j] = \pack('C', $int[1] & $mask); + } + return \inet_ntop($binary).'/'.$maskBits; + } + + /** + * Gets either the /32 (IPv4) or the /128 (IPv6) subnet of an IP address + * + * @return string + */ + public function getSubnet(): string { + if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) { + return $this->getIPv4Subnet( + $this->ip, + 32 + ); + } + return $this->getIPv6Subnet( + $this->ip, + 128 + ); + } + + /** + * Returns the specified IP address + * + * @return string + */ + public function __toString(): string { + return $this->ip; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/IBackend.php b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/IBackend.php new file mode 100644 index 0000000..57dd4e3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/IBackend.php @@ -0,0 +1,60 @@ + + * + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\RateLimiting\Backend; + +/** + * Interface IBackend defines a storage backend for the rate limiting data. It + * should be noted that writing and reading rate limiting data is an expensive + * operation and one should thus make sure to only use sufficient fast backends. + * + * @package OC\Security\RateLimiting\Backend + */ +interface IBackend { + /** + * Gets the amount of attempts within the last specified seconds + * + * @param string $methodIdentifier Identifier for the method + * @param string $userIdentifier Identifier for the user + * @param int $seconds Seconds to look back at + * @return int + */ + public function getAttempts(string $methodIdentifier, + string $userIdentifier, + int $seconds): int; + + /** + * Registers an attempt + * + * @param string $methodIdentifier Identifier for the method + * @param string $userIdentifier Identifier for the user + * @param int $period Period in seconds how long this attempt should be stored + */ + public function registerAttempt(string $methodIdentifier, + string $userIdentifier, + int $period); +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/MemoryCache.php b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/MemoryCache.php new file mode 100644 index 0000000..2893d31 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Backend/MemoryCache.php @@ -0,0 +1,129 @@ + + * + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\RateLimiting\Backend; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\ICache; +use OCP\ICacheFactory; + +/** + * Class MemoryCache uses the configured distributed memory cache for storing + * rate limiting data. + * + * @package OC\Security\RateLimiting\Backend + */ +class MemoryCache implements IBackend { + /** @var ICache */ + private $cache; + /** @var ITimeFactory */ + private $timeFactory; + + /** + * @param ICacheFactory $cacheFactory + * @param ITimeFactory $timeFactory + */ + public function __construct(ICacheFactory $cacheFactory, + ITimeFactory $timeFactory) { + $this->cache = $cacheFactory->createDistributed(__CLASS__); + $this->timeFactory = $timeFactory; + } + + /** + * @param string $methodIdentifier + * @param string $userIdentifier + * @return string + */ + private function hash(string $methodIdentifier, + string $userIdentifier): string { + return hash('sha512', $methodIdentifier . $userIdentifier); + } + + /** + * @param string $identifier + * @return array + */ + private function getExistingAttempts(string $identifier): array { + $cachedAttempts = $this->cache->get($identifier); + if ($cachedAttempts === null) { + return []; + } + + $cachedAttempts = json_decode($cachedAttempts, true); + if (\is_array($cachedAttempts)) { + return $cachedAttempts; + } + + return []; + } + + /** + * {@inheritDoc} + */ + public function getAttempts(string $methodIdentifier, + string $userIdentifier, + int $seconds): int { + $identifier = $this->hash($methodIdentifier, $userIdentifier); + $existingAttempts = $this->getExistingAttempts($identifier); + + $count = 0; + $currentTime = $this->timeFactory->getTime(); + /** @var array $existingAttempts */ + foreach ($existingAttempts as $attempt) { + if (($attempt + $seconds) > $currentTime) { + $count++; + } + } + + return $count; + } + + /** + * {@inheritDoc} + */ + public function registerAttempt(string $methodIdentifier, + string $userIdentifier, + int $period) { + $identifier = $this->hash($methodIdentifier, $userIdentifier); + $existingAttempts = $this->getExistingAttempts($identifier); + $currentTime = $this->timeFactory->getTime(); + + // Unset all attempts older than $period + foreach ($existingAttempts as $key => $attempt) { + if (($attempt + $period) < $currentTime) { + unset($existingAttempts[$key]); + } + } + $existingAttempts = array_values($existingAttempts); + + // Store the new attempt + $existingAttempts[] = (string)$currentTime; + $this->cache->set($identifier, json_encode($existingAttempts)); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php new file mode 100644 index 0000000..000056a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php @@ -0,0 +1,36 @@ + + * + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\RateLimiting\Exception; + +use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; +use OCP\AppFramework\Http; + +class RateLimitExceededException extends SecurityException { + public function __construct() { + parent::__construct('Rate limit exceeded', Http::STATUS_TOO_MANY_REQUESTS); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Limiter.php b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Limiter.php new file mode 100644 index 0000000..26671f5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/RateLimiting/Limiter.php @@ -0,0 +1,106 @@ + + * + * @author Lukas Reschke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\RateLimiting; + +use OC\Security\Normalizer\IpAddress; +use OC\Security\RateLimiting\Backend\IBackend; +use OC\Security\RateLimiting\Exception\RateLimitExceededException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IUser; + +class Limiter { + /** @var IBackend */ + private $backend; + /** @var ITimeFactory */ + private $timeFactory; + + /** + * @param ITimeFactory $timeFactory + * @param IBackend $backend + */ + public function __construct(ITimeFactory $timeFactory, + IBackend $backend) { + $this->backend = $backend; + $this->timeFactory = $timeFactory; + } + + /** + * @param string $methodIdentifier + * @param string $userIdentifier + * @param int $period + * @param int $limit + * @throws RateLimitExceededException + */ + private function register(string $methodIdentifier, + string $userIdentifier, + int $period, + int $limit): void { + $existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier, $period); + if ($existingAttempts >= $limit) { + throw new RateLimitExceededException(); + } + + $this->backend->registerAttempt($methodIdentifier, $userIdentifier, $this->timeFactory->getTime()); + } + + /** + * Registers attempt for an anonymous request + * + * @param string $identifier + * @param int $anonLimit + * @param int $anonPeriod + * @param string $ip + * @throws RateLimitExceededException + */ + public function registerAnonRequest(string $identifier, + int $anonLimit, + int $anonPeriod, + string $ip): void { + $ipSubnet = (new IpAddress($ip))->getSubnet(); + + $anonHashIdentifier = hash('sha512', 'anon::' . $identifier . $ipSubnet); + $this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit); + } + + /** + * Registers attempt for an authenticated request + * + * @param string $identifier + * @param int $userLimit + * @param int $userPeriod + * @param IUser $user + * @throws RateLimitExceededException + */ + public function registerUserRequest(string $identifier, + int $userLimit, + int $userPeriod, + IUser $user): void { + $userHashIdentifier = hash('sha512', 'user::' . $identifier . $user->getUID()); + $this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/SecureRandom.php b/docker/overlays/nextcloud/html/lib/private/Security/SecureRandom.php new file mode 100644 index 0000000..815b70c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/SecureRandom.php @@ -0,0 +1,62 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OCP\Security\ISecureRandom; + +/** + * Class SecureRandom provides a wrapper around the random_int function to generate + * secure random strings. For PHP 7 the native CSPRNG is used, older versions do + * use a fallback. + * + * Usage: + * \OC::$server->getSecureRandom()->generate(10); + * @package OC\Security + */ +class SecureRandom implements ISecureRandom { + /** + * Generate a random string of specified length. + * @param int $length The length of the generated string + * @param string $characters An optional list of characters to use if no character list is + * specified all valid base64 characters are used. + * @return string + */ + public function generate(int $length, + string $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'): string { + $maxCharIndex = \strlen($characters) - 1; + $randomString = ''; + + while ($length > 0) { + $randomNumber = \random_int(0, $maxCharIndex); + $randomString .= $characters[$randomNumber]; + $length--; + } + return $randomString; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Security/TrustedDomainHelper.php b/docker/overlays/nextcloud/html/lib/private/Security/TrustedDomainHelper.php new file mode 100644 index 0000000..8004bf7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Security/TrustedDomainHelper.php @@ -0,0 +1,110 @@ + + * @author Johannes Ernst + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Security; + +use OC\AppFramework\Http\Request; +use OCP\IConfig; + +/** + * Class TrustedDomain + * + * @package OC\Security + */ +class TrustedDomainHelper { + /** @var IConfig */ + private $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Strips a potential port from a domain (in format domain:port) + * @param string $host + * @return string $host without appended port + */ + private function getDomainWithoutPort($host) { + $pos = strrpos($host, ':'); + if ($pos !== false) { + $port = substr($host, $pos + 1); + if (is_numeric($port)) { + $host = substr($host, 0, $pos); + } + } + return $host; + } + + /** + * Checks whether a domain is considered as trusted from the list + * of trusted domains. If no trusted domains have been configured, returns + * true. + * This is used to prevent Host Header Poisoning. + * @param string $domainWithPort + * @return bool true if the given domain is trusted or if no trusted domains + * have been configured + */ + public function isTrustedDomain($domainWithPort) { + // overwritehost is always trusted + if ($this->config->getSystemValue('overwritehost') !== '') { + return true; + } + + $domain = $this->getDomainWithoutPort($domainWithPort); + + // Read trusted domains from config + $trustedList = $this->config->getSystemValue('trusted_domains', []); + if (!is_array($trustedList)) { + return false; + } + + // Always allow access from localhost + if (preg_match(Request::REGEX_LOCALHOST, $domain) === 1) { + return true; + } + // Reject misformed domains in any case + if (strpos($domain,'-') === 0 || strpos($domain,'..') !== false) { + return false; + } + // Match, allowing for * wildcards + foreach ($trustedList as $trusted) { + if (gettype($trusted) !== 'string') { + break; + } + $regex = '/^' . implode('[-\.a-zA-Z0-9]*', array_map(function ($v) { + return preg_quote($v, '/'); + }, explode('*', $trusted))) . '$/i'; + if (preg_match($regex, $domain) || preg_match($regex, $domainWithPort)) { + return true; + } + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Server.php b/docker/overlays/nextcloud/html/lib/private/Server.php new file mode 100644 index 0000000..75b3543 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Server.php @@ -0,0 +1,2297 @@ + + * + * @author Arne Hamann + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bernhard Posselt + * @author Bernhard Reiter + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Damjan Georgievski + * @author Daniel Kesselberg + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Julius Haertl + * @author Julius Härtl + * @author Lionel Elie Mamane + * @author Lukas Reschke + * @author Maxence Lange + * @author Michael Weimann + * @author Morris Jobke + * @author Piotr Mrówczyński + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author root + * @author Thomas Müller + * @author Thomas Tanghus + * @author Tobia De Koninck + * @author Vincent Petry + * @author Xheni Myrtaj + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\Accounts\AccountManager; +use OC\App\AppManager; +use OC\App\AppStore\Bundles\BundleFetcher; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\CategoryFetcher; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Utility\SimpleContainer; +use OC\AppFramework\Utility\TimeFactory; +use OC\Authentication\Events\LoginFailed; +use OC\Authentication\Listeners\LoginFailedListener; +use OC\Authentication\Listeners\UserLoggedInListener; +use OC\Authentication\LoginCredentials\Store; +use OC\Authentication\Token\IProvider; +use OC\Avatar\AvatarManager; +use OC\Collaboration\Collaborators\GroupPlugin; +use OC\Collaboration\Collaborators\MailPlugin; +use OC\Collaboration\Collaborators\RemoteGroupPlugin; +use OC\Collaboration\Collaborators\RemotePlugin; +use OC\Collaboration\Collaborators\UserPlugin; +use OC\Command\CronBus; +use OC\Comments\ManagerFactory as CommentsManagerFactory; +use OC\Contacts\ContactsMenu\ActionFactory; +use OC\Contacts\ContactsMenu\ContactsStore; +use OC\Dashboard\DashboardManager; +use OC\Diagnostics\EventLogger; +use OC\Diagnostics\QueryLogger; +use OC\Federation\CloudFederationFactory; +use OC\Federation\CloudFederationProviderManager; +use OC\Federation\CloudIdManager; +use OC\Files\Config\UserMountCache; +use OC\Files\Config\UserMountCacheListener; +use OC\Files\Mount\CacheMountProvider; +use OC\Files\Mount\LocalHomeMountProvider; +use OC\Files\Mount\ObjectHomeMountProvider; +use OC\Files\Mount\ObjectStorePreviewCacheMountProvider; +use OC\Files\Node\HookConnector; +use OC\Files\Node\LazyRoot; +use OC\Files\Node\Root; +use OC\Files\Storage\StorageFactory; +use OC\Files\Type\Loader; +use OC\Files\View; +use OC\FullTextSearch\FullTextSearchManager; +use OC\Http\Client\ClientService; +use OC\IntegrityCheck\Checker; +use OC\IntegrityCheck\Helpers\AppLocator; +use OC\IntegrityCheck\Helpers\EnvironmentHelper; +use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OC\Lock\DBLockingProvider; +use OC\Lock\MemcacheLockingProvider; +use OC\Lock\NoopLockingProvider; +use OC\Lockdown\LockdownManager; +use OC\Log\LogFactory; +use OC\Log\PsrLoggerAdapter; +use OC\Mail\Mailer; +use OC\Memcache\ArrayCache; +use OC\Memcache\Factory; +use OC\Notification\Manager; +use OC\OCS\DiscoveryService; +use OC\Preview\GeneratorHelper; +use OC\Remote\Api\ApiFactory; +use OC\Remote\InstanceFactory; +use OC\RichObjectStrings\Validator; +use OC\Security\Bruteforce\Throttler; +use OC\Security\CertificateManager; +use OC\Security\CredentialsManager; +use OC\Security\Crypto; +use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OC\Security\CSRF\CsrfTokenManager; +use OC\Security\CSRF\TokenStorage\SessionStorage; +use OC\Security\Hasher; +use OC\Security\SecureRandom; +use OC\Security\TrustedDomainHelper; +use OC\Session\CryptoWrapper; +use OC\Share20\ProviderFactory; +use OC\Share20\ShareHelper; +use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; +use OC\Tagging\TagMapper; +use OC\Template\JSCombiner; +use OCA\Theming\ImageManager; +use OCA\Theming\ThemingDefaults; +use OCA\Theming\Util; +use OCP\Accounts\IAccountManager; +use OCP\App\IAppManager; +use OCP\Authentication\LoginCredentials\IStore; +use OCP\BackgroundJob\IJobList; +use OCP\Collaboration\AutoComplete\IManager; +use OCP\Command\IBus; +use OCP\Comments\ICommentsManager; +use OCP\Contacts\ContactsMenu\IActionFactory; +use OCP\Contacts\ContactsMenu\IContactsStore; +use OCP\Dashboard\IDashboardManager; +use OCP\Defaults; +use OCP\Diagnostics\IEventLogger; +use OCP\Diagnostics\IQueryLogger; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudFederationFactory; +use OCP\Federation\ICloudFederationProviderManager; +use OCP\Federation\ICloudIdManager; +use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\IMimeTypeDetector; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountManager; +use OCP\Files\NotFoundException; +use OCP\Files\Storage\IStorageFactory; +use OCP\FullTextSearch\IFullTextSearchManager; +use OCP\GlobalScale\IConfig; +use OCP\Group\Events\BeforeGroupCreatedEvent; +use OCP\Group\Events\BeforeGroupDeletedEvent; +use OCP\Group\Events\BeforeUserAddedEvent; +use OCP\Group\Events\BeforeUserRemovedEvent; +use OCP\Group\Events\GroupCreatedEvent; +use OCP\Group\Events\GroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\Group\ISubAdmin; +use OCP\Http\Client\IClientService; +use OCP\IAppConfig; +use OCP\IAvatarManager; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IDateTimeFormatter; +use OCP\IDateTimeZone; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IInitialStateService; +use OCP\IL10N; +use OCP\ILogger; +use OCP\INavigationManager; +use OCP\IPreview; +use OCP\IRequest; +use OCP\ISearch; +use OCP\IServerContainer; +use OCP\ITagManager; +use OCP\ITempManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\L10N\IFactory; +use OCP\Lock\ILockingProvider; +use OCP\Log\ILogFactory; +use OCP\Mail\IMailer; +use OCP\Remote\Api\IApiFactory; +use OCP\Remote\IInstanceFactory; +use OCP\RichObjectStrings\IValidator; +use OCP\Route\IRouter; +use OCP\Security\IContentSecurityPolicyManager; +use OCP\Security\ICredentialsManager; +use OCP\Security\ICrypto; +use OCP\Security\IHasher; +use OCP\Security\ISecureRandom; +use OCP\Share\IShareHelper; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\User\Events\BeforePasswordUpdatedEvent; +use OCP\User\Events\BeforeUserCreatedEvent; +use OCP\User\Events\BeforeUserDeletedEvent; +use OCP\User\Events\BeforeUserLoggedInEvent; +use OCP\User\Events\BeforeUserLoggedInWithCookieEvent; +use OCP\User\Events\BeforeUserLoggedOutEvent; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\PostLoginEvent; +use OCP\User\Events\UserChangedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\Events\UserLoggedInEvent; +use OCP\User\Events\UserLoggedInWithCookieEvent; +use OCP\User\Events\UserLoggedOutEvent; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; +use OCA\Files_External\Service\UserStoragesService; +use OCA\Files_External\Service\UserGlobalStoragesService; +use OCA\Files_External\Service\GlobalStoragesService; +use OCA\Files_External\Service\BackendService; + +/** + * Class Server + * + * @package OC + * + * TODO: hookup all manager classes + */ +class Server extends ServerContainer implements IServerContainer { + + /** @var string */ + private $webRoot; + + /** + * @param string $webRoot + * @param \OC\Config $config + */ + public function __construct($webRoot, \OC\Config $config) { + parent::__construct(); + $this->webRoot = $webRoot; + + // To find out if we are running from CLI or not + $this->registerParameter('isCLI', \OC::$CLI); + $this->registerParameter('serverRoot', \OC::$SERVERROOT); + + $this->registerService(ContainerInterface::class, function (ContainerInterface $c) { + return $c; + }); + $this->registerService(\OCP\IServerContainer::class, function (ContainerInterface $c) { + return $c; + }); + + $this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CalendarManager', \OC\Calendar\Manager::class); + + $this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CalendarResourceBackendManager', \OC\Calendar\Resource\Manager::class); + + $this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CalendarRoomBackendManager', \OC\Calendar\Room\Manager::class); + + $this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('ContactsManager', \OCP\Contacts\IManager::class); + + $this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class); + + $this->registerAlias(IActionFactory::class, ActionFactory::class); + + + $this->registerService(IPreview::class, function (Server $c) { + return new PreviewManager( + $c->getConfig(), + $c->getRootFolder(), + new \OC\Preview\Storage\Root($c->getRootFolder(), $c->getSystemConfig()), + $c->getEventDispatcher(), + $c->getGeneratorHelper(), + $c->getSession()->get('user_id') + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('PreviewManager', IPreview::class); + + $this->registerService(\OC\Preview\Watcher::class, function (Server $c) { + return new \OC\Preview\Watcher( + new \OC\Preview\Storage\Root($c->getRootFolder(), $c->getSystemConfig()) + ); + }); + + $this->registerService(\OCP\Encryption\IManager::class, function (Server $c) { + $view = new View(); + $util = new Encryption\Util( + $view, + $c->getUserManager(), + $c->getGroupManager(), + $c->getConfig() + ); + return new Encryption\Manager( + $c->getConfig(), + $c->getLogger(), + $c->getL10N('core'), + new View(), + $util, + new ArrayCache() + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('EncryptionManager', \OCP\Encryption\IManager::class); + + $this->registerService('EncryptionFileHelper', function (Server $c) { + $util = new Encryption\Util( + new View(), + $c->getUserManager(), + $c->getGroupManager(), + $c->getConfig() + ); + return new Encryption\File( + $util, + $c->getRootFolder(), + $c->getShareManager() + ); + }); + + $this->registerService('EncryptionKeyStorage', function (Server $c) { + $view = new View(); + $util = new Encryption\Util( + $view, + $c->getUserManager(), + $c->getGroupManager(), + $c->getConfig() + ); + + return new Encryption\Keys\Storage($view, $util, $c->getCrypto(), $c->getConfig()); + }); + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('TagMapper', TagMapper::class); + + $this->registerAlias(\OCP\ITagManager::class, TagManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('TagManager', \OCP\ITagManager::class); + + $this->registerService('SystemTagManagerFactory', function (Server $c) { + $config = $c->getConfig(); + $factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class); + return new $factoryClass($this); + }); + $this->registerService(ISystemTagManager::class, function (Server $c) { + return $c->query('SystemTagManagerFactory')->getManager(); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class); + + $this->registerService(ISystemTagObjectMapper::class, function (Server $c) { + return $c->query('SystemTagManagerFactory')->getObjectMapper(); + }); + $this->registerService('RootFolder', function (Server $c) { + $manager = \OC\Files\Filesystem::getMountManager(null); + $view = new View(); + $root = new Root( + $manager, + $view, + null, + $c->getUserMountCache(), + $this->getLogger(), + $this->getUserManager() + ); + + $previewConnector = new \OC\Preview\WatcherConnector($root, $c->getSystemConfig()); + $previewConnector->connectWatcher(); + + return $root; + }); + $this->registerService(HookConnector::class, function (Server $c) { + return new HookConnector( + $c->query(IRootFolder::class), + new View(), + $c->query(\OC\EventDispatcher\SymfonyAdapter::class), + $c->query(IEventDispatcher::class) + ); + }); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('SystemTagObjectMapper', ISystemTagObjectMapper::class); + + $this->registerService(IRootFolder::class, function (Server $c) { + return new LazyRoot(function () use ($c) { + return $c->query('RootFolder'); + }); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('LazyRootFolder', IRootFolder::class); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('UserManager', \OC\User\Manager::class); + $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class); + + $this->registerService(\OCP\IGroupManager::class, function (Server $c) { + $groupManager = new \OC\Group\Manager($this->getUserManager(), $c->getEventDispatcher(), $this->getLogger()); + $groupManager->listen('\OC\Group', 'preCreate', function ($gid) { + \OC_Hook::emit('OC_Group', 'pre_createGroup', ['run' => true, 'gid' => $gid]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeGroupCreatedEvent($gid)); + }); + $groupManager->listen('\OC\Group', 'postCreate', function (\OC\Group\Group $group) { + \OC_Hook::emit('OC_User', 'post_createGroup', ['gid' => $group->getGID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new GroupCreatedEvent($group)); + }); + $groupManager->listen('\OC\Group', 'preDelete', function (\OC\Group\Group $group) { + \OC_Hook::emit('OC_Group', 'pre_deleteGroup', ['run' => true, 'gid' => $group->getGID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeGroupDeletedEvent($group)); + }); + $groupManager->listen('\OC\Group', 'postDelete', function (\OC\Group\Group $group) { + \OC_Hook::emit('OC_User', 'post_deleteGroup', ['gid' => $group->getGID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new GroupDeletedEvent($group)); + }); + $groupManager->listen('\OC\Group', 'preAddUser', function (\OC\Group\Group $group, \OC\User\User $user) { + \OC_Hook::emit('OC_Group', 'pre_addToGroup', ['run' => true, 'uid' => $user->getUID(), 'gid' => $group->getGID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserAddedEvent($group, $user)); + }); + $groupManager->listen('\OC\Group', 'postAddUser', function (\OC\Group\Group $group, \OC\User\User $user) { + \OC_Hook::emit('OC_Group', 'post_addToGroup', ['uid' => $user->getUID(), 'gid' => $group->getGID()]); + //Minimal fix to keep it backward compatible TODO: clean up all the GroupManager hooks + \OC_Hook::emit('OC_User', 'post_addToGroup', ['uid' => $user->getUID(), 'gid' => $group->getGID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserAddedEvent($group, $user)); + }); + $groupManager->listen('\OC\Group', 'preRemoveUser', function (\OC\Group\Group $group, \OC\User\User $user) { + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $user)); + }); + $groupManager->listen('\OC\Group', 'postRemoveUser', function (\OC\Group\Group $group, \OC\User\User $user) { + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserRemovedEvent($group, $user)); + }); + return $groupManager; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('GroupManager', \OCP\IGroupManager::class); + + $this->registerService(Store::class, function (Server $c) { + $session = $c->getSession(); + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + $tokenProvider = $c->query(IProvider::class); + } else { + $tokenProvider = null; + } + $logger = $c->getLogger(); + return new Store($session, $logger, $tokenProvider); + }); + $this->registerAlias(IStore::class, Store::class); + $this->registerAlias(IProvider::class, Authentication\Token\Manager::class); + + $this->registerService(\OC\User\Session::class, function (Server $c) { + $manager = $c->getUserManager(); + $session = new \OC\Session\Memory(''); + $timeFactory = new TimeFactory(); + // Token providers might require a working database. This code + // might however be called when ownCloud is not yet setup. + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + $defaultTokenProvider = $c->query(IProvider::class); + } else { + $defaultTokenProvider = null; + } + + $legacyDispatcher = $c->getEventDispatcher(); + + $userSession = new \OC\User\Session( + $manager, + $session, + $timeFactory, + $defaultTokenProvider, + $c->getConfig(), + $c->getSecureRandom(), + $c->getLockdownManager(), + $c->getLogger(), + $c->query(IEventDispatcher::class) + ); + $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { + \OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password)); + }); + $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); + }); + $userSession->listen('\OC\User', 'preDelete', function ($user) use ($legacyDispatcher) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]); + $legacyDispatcher->dispatch('OCP\IUser::preDelete', new GenericEvent($user)); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserDeletedEvent($user)); + }); + $userSession->listen('\OC\User', 'postDelete', function ($user) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserDeletedEvent($user)); + }); + $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforePasswordUpdatedEvent($user, $password, $recoveryPassword)); + }); + $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new PasswordUpdatedEvent($user, $password, $recoveryPassword)); + }); + $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) { + \OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password)); + }); + $userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserLoggedInEvent($user, $password, $isTokenLogin)); + }); + $userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) { + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid)); + }); + $userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password)); + }); + $userSession->listen('\OC\User', 'logout', function ($user) { + \OC_Hook::emit('OC_User', 'logout', []); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user)); + }); + $userSession->listen('\OC\User', 'postLogout', function ($user) { + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserLoggedOutEvent($user)); + }); + $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]); + + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher->dispatchTyped(new UserChangedEvent($user, $feature, $value, $oldValue)); + }); + return $userSession; + }); + $this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('UserSession', \OC\User\Session::class); + + $this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class); + + $this->registerAlias(INavigationManager::class, \OC\NavigationManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('NavigationManager', INavigationManager::class); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('AllConfig', \OC\AllConfig::class); + $this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class); + + $this->registerService(\OC\SystemConfig::class, function ($c) use ($config) { + return new \OC\SystemConfig($config); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('SystemConfig', \OC\SystemConfig::class); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('AppConfig', \OC\AppConfig::class); + $this->registerAlias(IAppConfig::class, \OC\AppConfig::class); + + $this->registerService(IFactory::class, function (Server $c) { + return new \OC\L10N\Factory( + $c->getConfig(), + $c->getRequest(), + $c->getUserSession(), + \OC::$SERVERROOT + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('L10NFactory', IFactory::class); + + $this->registerAlias(IURLGenerator::class, URLGenerator::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('URLGenerator', IURLGenerator::class); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('AppFetcher', AppFetcher::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CategoryFetcher', CategoryFetcher::class); + + $this->registerService(ICache::class, function ($c) { + return new Cache\File(); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('UserCache', ICache::class); + + $this->registerService(Factory::class, function (Server $c) { + $arrayCacheFactory = new \OC\Memcache\Factory('', $c->getLogger(), + ArrayCache::class, + ArrayCache::class, + ArrayCache::class + ); + $config = $c->getConfig(); + + if ($config->getSystemValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { + $v = \OC_App::getAppVersions(); + $v['core'] = implode(',', \OC_Util::getVersion()); + $version = implode(',', $v); + $instanceId = \OC_Util::getInstanceId(); + $path = \OC::$SERVERROOT; + $prefix = md5($instanceId . '-' . $version . '-' . $path); + return new \OC\Memcache\Factory($prefix, $c->getLogger(), + $config->getSystemValue('memcache.local', null), + $config->getSystemValue('memcache.distributed', null), + $config->getSystemValue('memcache.locking', null) + ); + } + return $arrayCacheFactory; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('MemCacheFactory', Factory::class); + $this->registerAlias(ICacheFactory::class, Factory::class); + + $this->registerService('RedisFactory', function (Server $c) { + $systemConfig = $c->getSystemConfig(); + return new RedisFactory($systemConfig); + }); + + $this->registerService(\OCP\Activity\IManager::class, function (Server $c) { + $l10n = $this->get(IFactory::class)->get('lib'); + return new \OC\Activity\Manager( + $c->getRequest(), + $c->getUserSession(), + $c->getConfig(), + $c->query(IValidator::class), + $l10n + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('ActivityManager', \OCP\Activity\IManager::class); + + $this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) { + return new \OC\Activity\EventMerger( + $c->getL10N('lib') + ); + }); + $this->registerAlias(IValidator::class, Validator::class); + + $this->registerService(AvatarManager::class, function (Server $c) { + return new AvatarManager( + $c->query(\OC\User\Manager::class), + $c->getAppDataDir('avatar'), + $c->getL10N('lib'), + $c->getLogger(), + $c->getConfig() + ); + }); + $this->registerAlias(IAvatarManager::class, AvatarManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('AvatarManager', AvatarManager::class); + + $this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class); + $this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class); + + $this->registerService(\OC\Log::class, function (Server $c) { + $logType = $c->query(AllConfig::class)->getSystemValue('log_type', 'file'); + $factory = new LogFactory($c, $this->getSystemConfig()); + $logger = $factory->get($logType); + $registry = $c->query(\OCP\Support\CrashReport\IRegistry::class); + + return new Log($logger, $this->getSystemConfig(), null, $registry); + }); + $this->registerAlias(ILogger::class, \OC\Log::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Logger', \OC\Log::class); + // PSR-3 logger + $this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class); + + $this->registerService(ILogFactory::class, function (Server $c) { + return new LogFactory($c, $this->getSystemConfig()); + }); + + $this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('JobList', IJobList::class); + + $this->registerService(IRouter::class, function (Server $c) { + $cacheFactory = $c->getMemCacheFactory(); + $logger = $c->getLogger(); + if ($cacheFactory->isLocalCacheAvailable()) { + $router = new \OC\Route\CachingRouter($cacheFactory->createLocal('route'), $logger); + } else { + $router = new \OC\Route\Router($logger); + } + return $router; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Router', IRouter::class); + + $this->registerAlias(ISearch::class, Search::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Search', ISearch::class); + + $this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) { + return new \OC\Security\RateLimiting\Backend\MemoryCache( + $this->getMemCacheFactory(), + new \OC\AppFramework\Utility\TimeFactory() + ); + }); + + $this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class); + + $this->registerAlias(ICrypto::class, Crypto::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Crypto', ICrypto::class); + + $this->registerAlias(IHasher::class, Hasher::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Hasher', IHasher::class); + + $this->registerAlias(ICredentialsManager::class, CredentialsManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CredentialsManager', ICredentialsManager::class); + + $this->registerService(IDBConnection::class, function (Server $c) { + $systemConfig = $c->getSystemConfig(); + $factory = new \OC\DB\ConnectionFactory($systemConfig); + $type = $systemConfig->getValue('dbtype', 'sqlite'); + if (!$factory->isValidType($type)) { + throw new \OC\DatabaseException('Invalid database type'); + } + $connectionParams = $factory->createConnectionParams(); + $connection = $factory->getConnection($type, $connectionParams); + $connection->getConfiguration()->setSQLLogger($c->getQueryLogger()); + return $connection; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('DatabaseConnection', IDBConnection::class); + + + $this->registerService(IClientService::class, function (Server $c) { + $user = \OC_User::getUser(); + $uid = $user ? $user : null; + return new ClientService( + $c->getConfig(), + $c->getLogger(), + new \OC\Security\CertificateManager( + $uid, + new View(), + $c->getConfig(), + $c->getLogger(), + $c->getSecureRandom() + ) + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('HttpClientService', IClientService::class); + $this->registerService(IEventLogger::class, function (Server $c) { + $eventLogger = new EventLogger(); + if ($c->getSystemConfig()->getValue('debug', false)) { + // In debug mode, module is being activated by default + $eventLogger->activate(); + } + return $eventLogger; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('EventLogger', IEventLogger::class); + + $this->registerService(IQueryLogger::class, function (Server $c) { + $queryLogger = new QueryLogger(); + if ($c->getSystemConfig()->getValue('debug', false)) { + // In debug mode, module is being activated by default + $queryLogger->activate(); + } + return $queryLogger; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('QueryLogger', IQueryLogger::class); + + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('TempManager', TempManager::class); + $this->registerAlias(ITempManager::class, TempManager::class); + + $this->registerService(AppManager::class, function (Server $c) { + return new \OC\App\AppManager( + $c->getUserSession(), + $c->getConfig(), + $c->query(\OC\AppConfig::class), + $c->getGroupManager(), + $c->getMemCacheFactory(), + $c->getEventDispatcher(), + $c->getLogger() + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('AppManager', AppManager::class); + $this->registerAlias(IAppManager::class, AppManager::class); + + $this->registerAlias(IDateTimeZone::class, DateTimeZone::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('DateTimeZone', IDateTimeZone::class); + + $this->registerService(IDateTimeFormatter::class, function (Server $c) { + $language = $c->getConfig()->getUserValue($c->getSession()->get('user_id'), 'core', 'lang', null); + + return new DateTimeFormatter( + $c->getDateTimeZone()->getTimeZone(), + $c->getL10N('lib', $language) + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('DateTimeFormatter', IDateTimeFormatter::class); + + $this->registerService(IUserMountCache::class, function (Server $c) { + $mountCache = new UserMountCache($c->getDatabaseConnection(), $c->getUserManager(), $c->getLogger()); + $listener = new UserMountCacheListener($mountCache); + $listener->listen($c->getUserManager()); + return $mountCache; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('UserMountCache', IUserMountCache::class); + + $this->registerService(IMountProviderCollection::class, function (Server $c) { + $loader = \OC\Files\Filesystem::getLoader(); + $mountCache = $c->query(IUserMountCache::class); + $manager = new \OC\Files\Config\MountProviderCollection($loader, $mountCache); + + // builtin providers + + $config = $c->getConfig(); + $logger = $c->getLogger(); + $manager->registerProvider(new CacheMountProvider($config)); + $manager->registerHomeProvider(new LocalHomeMountProvider()); + $manager->registerHomeProvider(new ObjectHomeMountProvider($config)); + $manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config)); + + return $manager; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('MountConfigManager', IMountProviderCollection::class); + + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('IniWrapper', IniGetWrapper::class); + $this->registerService('AsyncCommandBus', function (Server $c) { + $busClass = $c->getConfig()->getSystemValue('commandbus'); + if ($busClass) { + [$app, $class] = explode('::', $busClass, 2); + if ($c->getAppManager()->isInstalled($app)) { + \OC_App::loadApp($app); + return $c->query($class); + } else { + throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled"); + } + } else { + $jobList = $c->getJobList(); + return new CronBus($jobList); + } + }); + $this->registerAlias(IBus::class, 'AsyncCommandBus'); + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('TrustedDomainHelper', TrustedDomainHelper::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Throttler', Throttler::class); + $this->registerService('IntegrityCodeChecker', function (Server $c) { + // IConfig and IAppManager requires a working database. This code + // might however be called when ownCloud is not yet setup. + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + $config = $c->getConfig(); + $appManager = $c->getAppManager(); + } else { + $config = null; + $appManager = null; + } + + return new Checker( + new EnvironmentHelper(), + new FileAccessHelper(), + new AppLocator(), + $config, + $c->getMemCacheFactory(), + $appManager, + $c->getTempManager(), + $c->getMimeTypeDetector() + ); + }); + $this->registerService(\OCP\IRequest::class, function ($c) { + if (isset($this['urlParams'])) { + $urlParams = $this['urlParams']; + } else { + $urlParams = []; + } + + if (defined('PHPUNIT_RUN') && PHPUNIT_RUN + && in_array('fakeinput', stream_get_wrappers()) + ) { + $stream = 'fakeinput://data'; + } else { + $stream = 'php://input'; + } + + return new Request( + [ + 'get' => $_GET, + 'post' => $_POST, + 'files' => $_FILES, + 'server' => $_SERVER, + 'env' => $_ENV, + 'cookies' => $_COOKIE, + 'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) + ? $_SERVER['REQUEST_METHOD'] + : '', + 'urlParams' => $urlParams, + ], + $this->getSecureRandom(), + $this->getConfig(), + $this->getCsrfTokenManager(), + $stream + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Request', \OCP\IRequest::class); + + $this->registerService(IMailer::class, function (Server $c) { + return new Mailer( + $c->getConfig(), + $c->getLogger(), + $c->query(Defaults::class), + $c->getURLGenerator(), + $c->getL10N('lib'), + $c->query(IEventDispatcher::class), + $c->getL10NFactory() + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Mailer', IMailer::class); + + $this->registerService('LDAPProvider', function (Server $c) { + $config = $c->getConfig(); + $factoryClass = $config->getSystemValue('ldapProviderFactory', null); + if (is_null($factoryClass)) { + throw new \Exception('ldapProviderFactory not set'); + } + /** @var \OCP\LDAP\ILDAPProviderFactory $factory */ + $factory = new $factoryClass($this); + return $factory->getLDAPProvider(); + }); + $this->registerService(ILockingProvider::class, function (Server $c) { + $ini = $c->get(IniGetWrapper::class); + $config = $c->getConfig(); + $ttl = $config->getSystemValue('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time'))); + if ($config->getSystemValue('filelocking.enabled', true) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { + /** @var \OC\Memcache\Factory $memcacheFactory */ + $memcacheFactory = $c->getMemCacheFactory(); + $memcache = $memcacheFactory->createLocking('lock'); + if (!($memcache instanceof \OC\Memcache\NullCache)) { + return new MemcacheLockingProvider($memcache, $ttl); + } + return new DBLockingProvider( + $c->getDatabaseConnection(), + $c->getLogger(), + new TimeFactory(), + $ttl, + !\OC::$CLI + ); + } + return new NoopLockingProvider(); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('LockingProvider', ILockingProvider::class); + + $this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('MountManager', IMountManager::class); + + $this->registerService(IMimeTypeDetector::class, function (Server $c) { + return new \OC\Files\Type\Detection( + $c->getURLGenerator(), + $c->getLogger(), + \OC::$configDir, + \OC::$SERVERROOT . '/resources/config/' + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('MimeTypeDetector', IMimeTypeDetector::class); + + $this->registerAlias(IMimeTypeLoader::class, Loader::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('MimeTypeLoader', IMimeTypeLoader::class); + $this->registerService(BundleFetcher::class, function () { + return new BundleFetcher($this->getL10N('lib')); + }); + $this->registerAlias(\OCP\Notification\IManager::class, Manager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('NotificationManager', \OCP\Notification\IManager::class); + + $this->registerService(CapabilitiesManager::class, function (Server $c) { + $manager = new CapabilitiesManager($c->getLogger()); + $manager->registerCapability(function () use ($c) { + return new \OC\OCS\CoreCapabilities($c->getConfig()); + }); + $manager->registerCapability(function () use ($c) { + return $c->query(\OC\Security\Bruteforce\Capabilities::class); + }); + return $manager; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CapabilitiesManager', CapabilitiesManager::class); + + $this->registerService(ICommentsManager::class, function (Server $c) { + $config = $c->getConfig(); + $factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class); + /** @var \OCP\Comments\ICommentsManagerFactory $factory */ + $factory = new $factoryClass($this); + $manager = $factory->getManager(); + + $manager->registerDisplayNameResolver('user', function ($id) use ($c) { + $manager = $c->getUserManager(); + $user = $manager->get($id); + if (is_null($user)) { + $l = $c->getL10N('core'); + $displayName = $l->t('Unknown user'); + } else { + $displayName = $user->getDisplayName(); + } + return $displayName; + }); + + return $manager; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CommentsManager', ICommentsManager::class); + + $this->registerAlias(\OC_Defaults::class, 'ThemingDefaults'); + $this->registerService('ThemingDefaults', function (Server $c) { + /* + * Dark magic for autoloader. + * If we do a class_exists it will try to load the class which will + * make composer cache the result. Resulting in errors when enabling + * the theming app. + */ + $prefixes = \OC::$composerAutoloader->getPrefixesPsr4(); + if (isset($prefixes['OCA\\Theming\\'])) { + $classExists = true; + } else { + $classExists = false; + } + + if ($classExists && $c->getConfig()->getSystemValue('installed', false) && $c->getAppManager()->isInstalled('theming') && $c->getTrustedDomainHelper()->isTrustedDomain($c->getRequest()->getInsecureServerHost())) { + return new ThemingDefaults( + $c->getConfig(), + $c->getL10N('theming'), + $c->getURLGenerator(), + $c->getMemCacheFactory(), + new Util($c->getConfig(), $this->getAppManager(), $c->getAppDataDir('theming')), + new ImageManager($c->getConfig(), $c->getAppDataDir('theming'), $c->getURLGenerator(), $this->getMemCacheFactory(), $this->getLogger(), $this->getTempManager()), + $c->getAppManager(), + $c->getNavigationManager() + ); + } + return new \OC_Defaults(); + }); + $this->registerService(JSCombiner::class, function (Server $c) { + return new JSCombiner( + $c->getAppDataDir('js'), + $c->getURLGenerator(), + $this->getMemCacheFactory(), + $c->getSystemConfig(), + $c->getLogger() + ); + }); + $this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('EventDispatcher', \OC\EventDispatcher\SymfonyAdapter::class); + $this->registerAlias(EventDispatcherInterface::class, \OC\EventDispatcher\SymfonyAdapter::class); + + $this->registerService('CryptoWrapper', function (Server $c) { + // FIXME: Instantiiated here due to cyclic dependency + $request = new Request( + [ + 'get' => $_GET, + 'post' => $_POST, + 'files' => $_FILES, + 'server' => $_SERVER, + 'env' => $_ENV, + 'cookies' => $_COOKIE, + 'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) + ? $_SERVER['REQUEST_METHOD'] + : null, + ], + $c->getSecureRandom(), + $c->getConfig() + ); + + return new CryptoWrapper( + $c->getConfig(), + $c->getCrypto(), + $c->getSecureRandom(), + $request + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CsrfTokenManager', CsrfTokenManager::class); + $this->registerService(SessionStorage::class, function (Server $c) { + return new SessionStorage($c->getSession()); + }); + $this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('ContentSecurityPolicyManager', ContentSecurityPolicyManager::class); + + $this->registerService(\OCP\Share\IManager::class, function (Server $c) { + $config = $c->getConfig(); + $factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class); + /** @var \OCP\Share\IProviderFactory $factory */ + $factory = new $factoryClass($this); + + $manager = new \OC\Share20\Manager( + $c->getLogger(), + $c->getConfig(), + $c->getSecureRandom(), + $c->getHasher(), + $c->getMountManager(), + $c->getGroupManager(), + $c->getL10N('lib'), + $c->getL10NFactory(), + $factory, + $c->getUserManager(), + $c->getLazyRootFolder(), + $c->getEventDispatcher(), + $c->getMailer(), + $c->getURLGenerator(), + $c->getThemingDefaults(), + $c->query(IEventDispatcher::class) + ); + + return $manager; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('ShareManager', \OCP\Share\IManager::class); + + $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) { + $instance = new Collaboration\Collaborators\Search($c); + + // register default plugins + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]); + + return $instance; + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('CollaboratorSearch', \OCP\Collaboration\Collaborators\ISearch::class); + $this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class); + + $this->registerAlias(\OCP\Collaboration\AutoComplete\IManager::class, \OC\Collaboration\AutoComplete\Manager::class); + + $this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class); + $this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class); + + $this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class); + $this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class); + $this->registerService(\OC\Files\AppData\Factory::class, function (Server $c) { + return new \OC\Files\AppData\Factory( + $c->getRootFolder(), + $c->getSystemConfig() + ); + }); + + $this->registerService('LockdownManager', function (Server $c) { + return new LockdownManager(function () use ($c) { + return $c->getSession(); + }); + }); + + $this->registerService(\OCP\OCS\IDiscoveryService::class, function (Server $c) { + return new DiscoveryService($c->getMemCacheFactory(), $c->getHTTPClientService()); + }); + + $this->registerService(ICloudIdManager::class, function (Server $c) { + return new CloudIdManager(); + }); + + $this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class); + + $this->registerService(ICloudFederationProviderManager::class, function (Server $c) { + return new CloudFederationProviderManager($c->getAppManager(), $c->getHTTPClientService(), $c->getCloudIdManager(), $c->getLogger()); + }); + + $this->registerService(ICloudFederationFactory::class, function (Server $c) { + return new CloudFederationFactory(); + }); + + $this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('ControllerMethodReflector', \OCP\AppFramework\Utility\IControllerMethodReflector::class); + + $this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('TimeFactory', \OCP\AppFramework\Utility\ITimeFactory::class); + + $this->registerService(Defaults::class, function (Server $c) { + return new Defaults( + $c->getThemingDefaults() + ); + }); + /** @deprecated 19.0.0 */ + $this->registerDeprecatedAlias('Defaults', \OCP\Defaults::class); + + $this->registerService(\OCP\ISession::class, function (SimpleContainer $c) { + return $c->query(\OCP\IUserSession::class)->getSession(); + }); + + $this->registerService(IShareHelper::class, function (Server $c) { + return new ShareHelper( + $c->query(\OCP\Share\IManager::class) + ); + }); + + $this->registerService(Installer::class, function (Server $c) { + return new Installer( + $c->getAppFetcher(), + $c->getHTTPClientService(), + $c->getTempManager(), + $c->getLogger(), + $c->getConfig(), + \OC::$CLI + ); + }); + + $this->registerService(IApiFactory::class, function (Server $c) { + return new ApiFactory($c->getHTTPClientService()); + }); + + $this->registerService(IInstanceFactory::class, function (Server $c) { + $memcacheFactory = $c->getMemCacheFactory(); + return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->getHTTPClientService()); + }); + + $this->registerAlias(IContactsStore::class, ContactsStore::class); + $this->registerAlias(IAccountManager::class, AccountManager::class); + + $this->registerAlias(IStorageFactory::class, StorageFactory::class); + + $this->registerAlias(IDashboardManager::class, DashboardManager::class); + $this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class); + $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class); + + $this->registerAlias(ISubAdmin::class, SubAdmin::class); + + $this->registerAlias(IInitialStateService::class, InitialStateService::class); + + $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class); + + $this->connectDispatcher(); + } + + public function boot() { + /** @var HookConnector $hookConnector */ + $hookConnector = $this->query(HookConnector::class); + $hookConnector->viewToNode(); + } + + /** + * @return \OCP\Calendar\IManager + * @deprecated 20.0.0 + */ + public function getCalendarManager() { + return $this->query(\OC\Calendar\Manager::class); + } + + /** + * @return \OCP\Calendar\Resource\IManager + * @deprecated 20.0.0 + */ + public function getCalendarResourceBackendManager() { + return $this->query(\OC\Calendar\Resource\Manager::class); + } + + /** + * @return \OCP\Calendar\Room\IManager + * @deprecated 20.0.0 + */ + public function getCalendarRoomBackendManager() { + return $this->query(\OC\Calendar\Room\Manager::class); + } + + private function connectDispatcher() { + $dispatcher = $this->getEventDispatcher(); + + // Delete avatar on user deletion + $dispatcher->addListener('OCP\IUser::preDelete', function (GenericEvent $e) { + $logger = $this->getLogger(); + $manager = $this->getAvatarManager(); + /** @var IUser $user */ + $user = $e->getSubject(); + + try { + $avatar = $manager->getAvatar($user->getUID()); + $avatar->remove(); + } catch (NotFoundException $e) { + // no avatar to remove + } catch (\Exception $e) { + // Ignore exceptions + $logger->info('Could not cleanup avatar of ' . $user->getUID()); + } + }); + + $dispatcher->addListener('OCP\IUser::changeUser', function (GenericEvent $e) { + $manager = $this->getAvatarManager(); + /** @var IUser $user */ + $user = $e->getSubject(); + $feature = $e->getArgument('feature'); + $oldValue = $e->getArgument('oldValue'); + $value = $e->getArgument('value'); + + // We only change the avatar on display name changes + if ($feature !== 'displayName') { + return; + } + + try { + $avatar = $manager->getAvatar($user->getUID()); + $avatar->userChanged($feature, $oldValue, $value); + } catch (NotFoundException $e) { + // no avatar to remove + } + }); + + /** @var IEventDispatcher $eventDispatched */ + $eventDispatched = $this->query(IEventDispatcher::class); + $eventDispatched->addServiceListener(LoginFailed::class, LoginFailedListener::class); + $eventDispatched->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); + } + + /** + * @return \OCP\Contacts\IManager + * @deprecated 20.0.0 + */ + public function getContactsManager() { + return $this->query(\OCP\Contacts\IManager::class); + } + + /** + * @return \OC\Encryption\Manager + * @deprecated 20.0.0 + */ + public function getEncryptionManager() { + return $this->query(\OCP\Encryption\IManager::class); + } + + /** + * @return \OC\Encryption\File + * @deprecated 20.0.0 + */ + public function getEncryptionFilesHelper() { + return $this->query('EncryptionFileHelper'); + } + + /** + * @return \OCP\Encryption\Keys\IStorage + * @deprecated 20.0.0 + */ + public function getEncryptionKeyStorage() { + return $this->query('EncryptionKeyStorage'); + } + + /** + * The current request object holding all information about the request + * currently being processed is returned from this method. + * In case the current execution was not initiated by a web request null is returned + * + * @return \OCP\IRequest + * @deprecated 20.0.0 + */ + public function getRequest() { + return $this->query(IRequest::class); + } + + /** + * Returns the preview manager which can create preview images for a given file + * + * @return IPreview + * @deprecated 20.0.0 + */ + public function getPreviewManager() { + return $this->query(IPreview::class); + } + + /** + * Returns the tag manager which can get and set tags for different object types + * + * @see \OCP\ITagManager::load() + * @return ITagManager + * @deprecated 20.0.0 + */ + public function getTagManager() { + return $this->query(ITagManager::class); + } + + /** + * Returns the system-tag manager + * + * @return ISystemTagManager + * + * @since 9.0.0 + * @deprecated 20.0.0 + */ + public function getSystemTagManager() { + return $this->query(ISystemTagManager::class); + } + + /** + * Returns the system-tag object mapper + * + * @return ISystemTagObjectMapper + * + * @since 9.0.0 + * @deprecated 20.0.0 + */ + public function getSystemTagObjectMapper() { + return $this->query(ISystemTagObjectMapper::class); + } + + /** + * Returns the avatar manager, used for avatar functionality + * + * @return IAvatarManager + * @deprecated 20.0.0 + */ + public function getAvatarManager() { + return $this->query(IAvatarManager::class); + } + + /** + * Returns the root folder of ownCloud's data directory + * + * @return IRootFolder + * @deprecated 20.0.0 + */ + public function getRootFolder() { + return $this->query(IRootFolder::class); + } + + /** + * Returns the root folder of ownCloud's data directory + * This is the lazy variant so this gets only initialized once it + * is actually used. + * + * @return IRootFolder + * @deprecated 20.0.0 + */ + public function getLazyRootFolder() { + return $this->query(IRootFolder::class); + } + + /** + * Returns a view to ownCloud's files folder + * + * @param string $userId user ID + * @return \OCP\Files\Folder|null + * @deprecated 20.0.0 + */ + public function getUserFolder($userId = null) { + if ($userId === null) { + $user = $this->getUserSession()->getUser(); + if (!$user) { + return null; + } + $userId = $user->getUID(); + } + $root = $this->getRootFolder(); + return $root->getUserFolder($userId); + } + + /** + * @return \OC\User\Manager + * @deprecated 20.0.0 + */ + public function getUserManager() { + return $this->query(IUserManager::class); + } + + /** + * @return \OC\Group\Manager + * @deprecated 20.0.0 + */ + public function getGroupManager() { + return $this->query(IGroupManager::class); + } + + /** + * @return \OC\User\Session + * @deprecated 20.0.0 + */ + public function getUserSession() { + return $this->query(IUserSession::class); + } + + /** + * @return \OCP\ISession + * @deprecated 20.0.0 + */ + public function getSession() { + return $this->getUserSession()->getSession(); + } + + /** + * @param \OCP\ISession $session + */ + public function setSession(\OCP\ISession $session) { + $this->query(SessionStorage::class)->setSession($session); + $this->getUserSession()->setSession($session); + $this->query(Store::class)->setSession($session); + } + + /** + * @return \OC\Authentication\TwoFactorAuth\Manager + * @deprecated 20.0.0 + */ + public function getTwoFactorAuthManager() { + return $this->query(\OC\Authentication\TwoFactorAuth\Manager::class); + } + + /** + * @return \OC\NavigationManager + * @deprecated 20.0.0 + */ + public function getNavigationManager() { + return $this->query(INavigationManager::class); + } + + /** + * @return \OCP\IConfig + * @deprecated 20.0.0 + */ + public function getConfig() { + return $this->query(AllConfig::class); + } + + /** + * @return \OC\SystemConfig + * @deprecated 20.0.0 + */ + public function getSystemConfig() { + return $this->query(SystemConfig::class); + } + + /** + * Returns the app config manager + * + * @return IAppConfig + * @deprecated 20.0.0 + */ + public function getAppConfig() { + return $this->query(IAppConfig::class); + } + + /** + * @return IFactory + * @deprecated 20.0.0 + */ + public function getL10NFactory() { + return $this->query(IFactory::class); + } + + /** + * get an L10N instance + * + * @param string $app appid + * @param string $lang + * @return IL10N + * @deprecated 20.0.0 + */ + public function getL10N($app, $lang = null) { + return $this->getL10NFactory()->get($app, $lang); + } + + /** + * @return IURLGenerator + * @deprecated 20.0.0 + */ + public function getURLGenerator() { + return $this->query(IURLGenerator::class); + } + + /** + * @return AppFetcher + * @deprecated 20.0.0 + */ + public function getAppFetcher() { + return $this->query(AppFetcher::class); + } + + /** + * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use + * getMemCacheFactory() instead. + * + * @return ICache + * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache + */ + public function getCache() { + return $this->query(ICache::class); + } + + /** + * Returns an \OCP\CacheFactory instance + * + * @return \OCP\ICacheFactory + * @deprecated 20.0.0 + */ + public function getMemCacheFactory() { + return $this->query(Factory::class); + } + + /** + * Returns an \OC\RedisFactory instance + * + * @return \OC\RedisFactory + * @deprecated 20.0.0 + */ + public function getGetRedisFactory() { + return $this->query('RedisFactory'); + } + + + /** + * Returns the current session + * + * @return \OCP\IDBConnection + * @deprecated 20.0.0 + */ + public function getDatabaseConnection() { + return $this->query(IDBConnection::class); + } + + /** + * Returns the activity manager + * + * @return \OCP\Activity\IManager + * @deprecated 20.0.0 + */ + public function getActivityManager() { + return $this->query(\OCP\Activity\IManager::class); + } + + /** + * Returns an job list for controlling background jobs + * + * @return IJobList + * @deprecated 20.0.0 + */ + public function getJobList() { + return $this->query(IJobList::class); + } + + /** + * Returns a logger instance + * + * @return ILogger + * @deprecated 20.0.0 + */ + public function getLogger() { + return $this->query(ILogger::class); + } + + /** + * @return ILogFactory + * @throws \OCP\AppFramework\QueryException + * @deprecated 20.0.0 + */ + public function getLogFactory() { + return $this->query(ILogFactory::class); + } + + /** + * Returns a router for generating and matching urls + * + * @return IRouter + * @deprecated 20.0.0 + */ + public function getRouter() { + return $this->query(IRouter::class); + } + + /** + * Returns a search instance + * + * @return ISearch + * @deprecated 20.0.0 + */ + public function getSearch() { + return $this->query(ISearch::class); + } + + /** + * Returns a SecureRandom instance + * + * @return \OCP\Security\ISecureRandom + * @deprecated 20.0.0 + */ + public function getSecureRandom() { + return $this->query(ISecureRandom::class); + } + + /** + * Returns a Crypto instance + * + * @return ICrypto + * @deprecated 20.0.0 + */ + public function getCrypto() { + return $this->query(ICrypto::class); + } + + /** + * Returns a Hasher instance + * + * @return IHasher + * @deprecated 20.0.0 + */ + public function getHasher() { + return $this->query(IHasher::class); + } + + /** + * Returns a CredentialsManager instance + * + * @return ICredentialsManager + * @deprecated 20.0.0 + */ + public function getCredentialsManager() { + return $this->query(ICredentialsManager::class); + } + + /** + * Get the certificate manager for the user + * + * @param string $userId (optional) if not specified the current loggedin user is used, use null to get the system certificate manager + * @return \OCP\ICertificateManager | null if $uid is null and no user is logged in + * @deprecated 20.0.0 + */ + public function getCertificateManager($userId = '') { + if ($userId === '') { + $userSession = $this->getUserSession(); + $user = $userSession->getUser(); + if (is_null($user)) { + return null; + } + $userId = $user->getUID(); + } + return new CertificateManager( + $userId, + new View(), + $this->getConfig(), + $this->getLogger(), + $this->getSecureRandom() + ); + } + + /** + * Returns an instance of the HTTP client service + * + * @return IClientService + * @deprecated 20.0.0 + */ + public function getHTTPClientService() { + return $this->query(IClientService::class); + } + + /** + * Create a new event source + * + * @return \OCP\IEventSource + * @deprecated 20.0.0 + */ + public function createEventSource() { + return new \OC_EventSource(); + } + + /** + * Get the active event logger + * + * The returned logger only logs data when debug mode is enabled + * + * @return IEventLogger + * @deprecated 20.0.0 + */ + public function getEventLogger() { + return $this->query(IEventLogger::class); + } + + /** + * Get the active query logger + * + * The returned logger only logs data when debug mode is enabled + * + * @return IQueryLogger + * @deprecated 20.0.0 + */ + public function getQueryLogger() { + return $this->query(IQueryLogger::class); + } + + /** + * Get the manager for temporary files and folders + * + * @return \OCP\ITempManager + * @deprecated 20.0.0 + */ + public function getTempManager() { + return $this->query(ITempManager::class); + } + + /** + * Get the app manager + * + * @return \OCP\App\IAppManager + * @deprecated 20.0.0 + */ + public function getAppManager() { + return $this->query(IAppManager::class); + } + + /** + * Creates a new mailer + * + * @return IMailer + * @deprecated 20.0.0 + */ + public function getMailer() { + return $this->query(IMailer::class); + } + + /** + * Get the webroot + * + * @return string + * @deprecated 20.0.0 + */ + public function getWebRoot() { + return $this->webRoot; + } + + /** + * @return \OC\OCSClient + * @deprecated 20.0.0 + */ + public function getOcsClient() { + return $this->query('OcsClient'); + } + + /** + * @return IDateTimeZone + * @deprecated 20.0.0 + */ + public function getDateTimeZone() { + return $this->query(IDateTimeZone::class); + } + + /** + * @return IDateTimeFormatter + * @deprecated 20.0.0 + */ + public function getDateTimeFormatter() { + return $this->query(IDateTimeFormatter::class); + } + + /** + * @return IMountProviderCollection + * @deprecated 20.0.0 + */ + public function getMountProviderCollection() { + return $this->query(IMountProviderCollection::class); + } + + /** + * Get the IniWrapper + * + * @return IniGetWrapper + * @deprecated 20.0.0 + */ + public function getIniWrapper() { + return $this->query(IniGetWrapper::class); + } + + /** + * @return \OCP\Command\IBus + * @deprecated 20.0.0 + */ + public function getCommandBus() { + return $this->query('AsyncCommandBus'); + } + + /** + * Get the trusted domain helper + * + * @return TrustedDomainHelper + * @deprecated 20.0.0 + */ + public function getTrustedDomainHelper() { + return $this->query(TrustedDomainHelper::class); + } + + /** + * Get the locking provider + * + * @return ILockingProvider + * @since 8.1.0 + * @deprecated 20.0.0 + */ + public function getLockingProvider() { + return $this->query(ILockingProvider::class); + } + + /** + * @return IMountManager + * @deprecated 20.0.0 + **/ + public function getMountManager() { + return $this->query(IMountManager::class); + } + + /** + * @return IUserMountCache + * @deprecated 20.0.0 + */ + public function getUserMountCache() { + return $this->query(IUserMountCache::class); + } + + /** + * Get the MimeTypeDetector + * + * @return IMimeTypeDetector + * @deprecated 20.0.0 + */ + public function getMimeTypeDetector() { + return $this->query(IMimeTypeDetector::class); + } + + /** + * Get the MimeTypeLoader + * + * @return IMimeTypeLoader + * @deprecated 20.0.0 + */ + public function getMimeTypeLoader() { + return $this->query(IMimeTypeLoader::class); + } + + /** + * Get the manager of all the capabilities + * + * @return CapabilitiesManager + * @deprecated 20.0.0 + */ + public function getCapabilitiesManager() { + return $this->query(CapabilitiesManager::class); + } + + /** + * Get the EventDispatcher + * + * @return EventDispatcherInterface + * @since 8.2.0 + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher + */ + public function getEventDispatcher() { + return $this->query(\OC\EventDispatcher\SymfonyAdapter::class); + } + + /** + * Get the Notification Manager + * + * @return \OCP\Notification\IManager + * @since 8.2.0 + * @deprecated 20.0.0 + */ + public function getNotificationManager() { + return $this->query(\OCP\Notification\IManager::class); + } + + /** + * @return ICommentsManager + * @deprecated 20.0.0 + */ + public function getCommentsManager() { + return $this->query(ICommentsManager::class); + } + + /** + * @return \OCA\Theming\ThemingDefaults + * @deprecated 20.0.0 + */ + public function getThemingDefaults() { + return $this->query('ThemingDefaults'); + } + + /** + * @return \OC\IntegrityCheck\Checker + * @deprecated 20.0.0 + */ + public function getIntegrityCodeChecker() { + return $this->query('IntegrityCodeChecker'); + } + + /** + * @return \OC\Session\CryptoWrapper + * @deprecated 20.0.0 + */ + public function getSessionCryptoWrapper() { + return $this->query('CryptoWrapper'); + } + + /** + * @return CsrfTokenManager + * @deprecated 20.0.0 + */ + public function getCsrfTokenManager() { + return $this->query(CsrfTokenManager::class); + } + + /** + * @return Throttler + * @deprecated 20.0.0 + */ + public function getBruteForceThrottler() { + return $this->query(Throttler::class); + } + + /** + * @return IContentSecurityPolicyManager + * @deprecated 20.0.0 + */ + public function getContentSecurityPolicyManager() { + return $this->query(ContentSecurityPolicyManager::class); + } + + /** + * @return ContentSecurityPolicyNonceManager + * @deprecated 20.0.0 + */ + public function getContentSecurityPolicyNonceManager() { + return $this->query(ContentSecurityPolicyNonceManager::class); + } + + /** + * Not a public API as of 8.2, wait for 9.0 + * + * @return \OCA\Files_External\Service\BackendService + * @deprecated 20.0.0 + */ + public function getStoragesBackendService() { + return $this->query(BackendService::class); + } + + /** + * Not a public API as of 8.2, wait for 9.0 + * + * @return \OCA\Files_External\Service\GlobalStoragesService + * @deprecated 20.0.0 + */ + public function getGlobalStoragesService() { + return $this->query(GlobalStoragesService::class); + } + + /** + * Not a public API as of 8.2, wait for 9.0 + * + * @return \OCA\Files_External\Service\UserGlobalStoragesService + * @deprecated 20.0.0 + */ + public function getUserGlobalStoragesService() { + return $this->query(UserGlobalStoragesService::class); + } + + /** + * Not a public API as of 8.2, wait for 9.0 + * + * @return \OCA\Files_External\Service\UserStoragesService + * @deprecated 20.0.0 + */ + public function getUserStoragesService() { + return $this->query(UserStoragesService::class); + } + + /** + * @return \OCP\Share\IManager + * @deprecated 20.0.0 + */ + public function getShareManager() { + return $this->query(\OCP\Share\IManager::class); + } + + /** + * @return \OCP\Collaboration\Collaborators\ISearch + * @deprecated 20.0.0 + */ + public function getCollaboratorSearch() { + return $this->query(\OCP\Collaboration\Collaborators\ISearch::class); + } + + /** + * @return \OCP\Collaboration\AutoComplete\IManager + * @deprecated 20.0.0 + */ + public function getAutoCompleteManager() { + return $this->query(IManager::class); + } + + /** + * Returns the LDAP Provider + * + * @return \OCP\LDAP\ILDAPProvider + * @deprecated 20.0.0 + */ + public function getLDAPProvider() { + return $this->query('LDAPProvider'); + } + + /** + * @return \OCP\Settings\IManager + * @deprecated 20.0.0 + */ + public function getSettingsManager() { + return $this->query(\OC\Settings\Manager::class); + } + + /** + * @return \OCP\Files\IAppData + * @deprecated 20.0.0 + */ + public function getAppDataDir($app) { + /** @var \OC\Files\AppData\Factory $factory */ + $factory = $this->query(\OC\Files\AppData\Factory::class); + return $factory->get($app); + } + + /** + * @return \OCP\Lockdown\ILockdownManager + * @deprecated 20.0.0 + */ + public function getLockdownManager() { + return $this->query('LockdownManager'); + } + + /** + * @return \OCP\Federation\ICloudIdManager + * @deprecated 20.0.0 + */ + public function getCloudIdManager() { + return $this->query(ICloudIdManager::class); + } + + /** + * @return \OCP\GlobalScale\IConfig + * @deprecated 20.0.0 + */ + public function getGlobalScaleConfig() { + return $this->query(IConfig::class); + } + + /** + * @return \OCP\Federation\ICloudFederationProviderManager + * @deprecated 20.0.0 + */ + public function getCloudFederationProviderManager() { + return $this->query(ICloudFederationProviderManager::class); + } + + /** + * @return \OCP\Remote\Api\IApiFactory + * @deprecated 20.0.0 + */ + public function getRemoteApiFactory() { + return $this->query(IApiFactory::class); + } + + /** + * @return \OCP\Federation\ICloudFederationFactory + * @deprecated 20.0.0 + */ + public function getCloudFederationFactory() { + return $this->query(ICloudFederationFactory::class); + } + + /** + * @return \OCP\Remote\IInstanceFactory + * @deprecated 20.0.0 + */ + public function getRemoteInstanceFactory() { + return $this->query(IInstanceFactory::class); + } + + /** + * @return IStorageFactory + * @deprecated 20.0.0 + */ + public function getStorageFactory() { + return $this->query(IStorageFactory::class); + } + + /** + * Get the Preview GeneratorHelper + * + * @return GeneratorHelper + * @since 17.0.0 + * @deprecated 20.0.0 + */ + public function getGeneratorHelper() { + return $this->query(\OC\Preview\GeneratorHelper::class); + } + + private function registerDeprecatedAlias(string $alias, string $target) { + $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias) { + try { + /** @var ILogger $logger */ + $logger = $container->get(ILogger::class); + $logger->debug('The requested alias "' . $alias . '" is depreacted. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']); + } catch (ContainerExceptionInterface $e) { + // Could not get logger. Continue + } + + return $container->get($target); + }, false); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/ServerContainer.php b/docker/overlays/nextcloud/html/lib/private/ServerContainer.php new file mode 100644 index 0000000..72275ac --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/ServerContainer.php @@ -0,0 +1,176 @@ + + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\AppFramework\App; +use OC\AppFramework\DependencyInjection\DIContainer; +use OC\AppFramework\Utility\SimpleContainer; +use OCP\AppFramework\QueryException; +use function explode; +use function strtolower; + +/** + * Class ServerContainer + * + * @package OC + */ +class ServerContainer extends SimpleContainer { + /** @var DIContainer[] */ + protected $appContainers; + + /** @var string[] */ + protected $hasNoAppContainer; + + /** @var string[] */ + protected $namespaces; + + /** + * ServerContainer constructor. + */ + public function __construct() { + parent::__construct(); + $this->appContainers = []; + $this->namespaces = []; + $this->hasNoAppContainer = []; + } + + /** + * @param string $appName + * @param string $appNamespace + */ + public function registerNamespace(string $appName, string $appNamespace): void { + // Cut of OCA\ and lowercase + $appNamespace = strtolower(substr($appNamespace, strrpos($appNamespace, '\\') + 1)); + $this->namespaces[$appNamespace] = $appName; + } + + /** + * @param string $appName + * @param DIContainer $container + */ + public function registerAppContainer(string $appName, DIContainer $container): void { + $this->appContainers[strtolower(App::buildAppNamespace($appName, ''))] = $container; + } + + /** + * @param string $appName + * @return DIContainer + * @throws QueryException + */ + public function getRegisteredAppContainer(string $appName): DIContainer { + if (isset($this->appContainers[strtolower(App::buildAppNamespace($appName, ''))])) { + return $this->appContainers[strtolower(App::buildAppNamespace($appName, ''))]; + } + + throw new QueryException(); + } + + /** + * @param string $namespace + * @param string $sensitiveNamespace + * @return DIContainer + * @throws QueryException + */ + protected function getAppContainer(string $namespace, string $sensitiveNamespace): DIContainer { + if (isset($this->appContainers[$namespace])) { + return $this->appContainers[$namespace]; + } + + if (isset($this->namespaces[$namespace])) { + if (!isset($this->hasNoAppContainer[$namespace])) { + $applicationClassName = 'OCA\\' . $sensitiveNamespace . '\\AppInfo\\Application'; + if (class_exists($applicationClassName)) { + $app = new $applicationClassName(); + if (isset($this->appContainers[$namespace])) { + $this->appContainers[$namespace]->offsetSet($applicationClassName, $app); + return $this->appContainers[$namespace]; + } + } + $this->hasNoAppContainer[$namespace] = true; + } + + return new DIContainer($this->namespaces[$namespace]); + } + throw new QueryException(); + } + + public function has($id, bool $noRecursion = false): bool { + if (!$noRecursion && ($appContainer = $this->getAppContainerForService($id)) !== null) { + return $appContainer->has($id); + } + + return parent::has($id); + } + + /** + * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get + */ + public function query(string $name, bool $autoload = true) { + $name = $this->sanitizeName($name); + + // In case the service starts with OCA\ we try to find the service in + // the apps container first. + if (($appContainer = $this->getAppContainerForService($name)) !== null) { + try { + return $appContainer->queryNoFallback($name); + } catch (QueryException $e) { + // Didn't find the service or the respective app container, + // ignore it and fall back to the core container. + } + } elseif (strpos($name, 'OC\\Settings\\') === 0 && substr_count($name, '\\') >= 3) { + $segments = explode('\\', $name); + try { + $appContainer = $this->getAppContainer(strtolower($segments[1]), $segments[1]); + return $appContainer->queryNoFallback($name); + } catch (QueryException $e) { + // Didn't find the service or the respective app container, + // ignore it and fall back to the core container. + } + } + + return parent::query($name, $autoload); + } + + /** + * @internal + * @param string $id + * @return DIContainer|null + */ + public function getAppContainerForService(string $id): ?DIContainer { + if (strpos($id, 'OCA\\') !== 0 || substr_count($id, '\\') < 2) { + return null; + } + + try { + [,$namespace,] = explode('\\', $id); + return $this->getAppContainer(strtolower($namespace), $namespace); + } catch (QueryException $e) { + return null; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/ServerNotAvailableException.php b/docker/overlays/nextcloud/html/lib/private/ServerNotAvailableException.php new file mode 100644 index 0000000..d061888 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/ServerNotAvailableException.php @@ -0,0 +1,26 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class ServerNotAvailableException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/ServiceUnavailableException.php b/docker/overlays/nextcloud/html/lib/private/ServiceUnavailableException.php new file mode 100644 index 0000000..0598ca4 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/ServiceUnavailableException.php @@ -0,0 +1,27 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class ServiceUnavailableException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Session/CryptoSessionData.php b/docker/overlays/nextcloud/html/lib/private/Session/CryptoSessionData.php new file mode 100644 index 0000000..fc7693b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Session/CryptoSessionData.php @@ -0,0 +1,216 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Session; + +use OCP\ISession; +use OCP\Security\ICrypto; +use OCP\Session\Exceptions\SessionNotAvailableException; + +/** + * Class CryptoSessionData + * + * @package OC\Session + */ +class CryptoSessionData implements \ArrayAccess, ISession { + /** @var ISession */ + protected $session; + /** @var \OCP\Security\ICrypto */ + protected $crypto; + /** @var string */ + protected $passphrase; + /** @var array */ + protected $sessionValues; + /** @var bool */ + protected $isModified = false; + public const encryptedSessionName = 'encrypted_session_data'; + + /** + * @param ISession $session + * @param ICrypto $crypto + * @param string $passphrase + */ + public function __construct(ISession $session, + ICrypto $crypto, + string $passphrase) { + $this->crypto = $crypto; + $this->session = $session; + $this->passphrase = $passphrase; + $this->initializeSession(); + } + + /** + * Close session if class gets destructed + */ + public function __destruct() { + try { + $this->close(); + } catch (SessionNotAvailableException $e) { + // This exception can occur if session is already closed + // So it is safe to ignore it and let the garbage collector to proceed + } + } + + protected function initializeSession() { + $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: ''; + try { + $this->sessionValues = json_decode( + $this->crypto->decrypt($encryptedSessionData, $this->passphrase), + true + ); + } catch (\Exception $e) { + $this->sessionValues = []; + } + } + + /** + * Set a value in the session + * + * @param string $key + * @param mixed $value + */ + public function set(string $key, $value) { + $this->sessionValues[$key] = $value; + $this->isModified = true; + } + + /** + * Get a value from the session + * + * @param string $key + * @return string|null Either the value or null + */ + public function get(string $key) { + if (isset($this->sessionValues[$key])) { + return $this->sessionValues[$key]; + } + + return null; + } + + /** + * Check if a named key exists in the session + * + * @param string $key + * @return bool + */ + public function exists(string $key): bool { + return isset($this->sessionValues[$key]); + } + + /** + * Remove a $key/$value pair from the session + * + * @param string $key + */ + public function remove(string $key) { + $this->isModified = true; + unset($this->sessionValues[$key]); + $this->session->remove(self::encryptedSessionName); + } + + /** + * Reset and recreate the session + */ + public function clear() { + $requesttoken = $this->get('requesttoken'); + $this->sessionValues = []; + if ($requesttoken !== null) { + $this->set('requesttoken', $requesttoken); + } + $this->isModified = true; + $this->session->clear(); + } + + /** + * Wrapper around session_regenerate_id + * + * @param bool $deleteOldSession Whether to delete the old associated session file or not. + * @param bool $updateToken Wheater to update the associated auth token + * @return void + */ + public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { + $this->session->regenerateId($deleteOldSession, $updateToken); + } + + /** + * Wrapper around session_id + * + * @return string + * @throws SessionNotAvailableException + * @since 9.1.0 + */ + public function getId(): string { + return $this->session->getId(); + } + + /** + * Close the session and release the lock, also writes all changed data in batch + */ + public function close() { + if ($this->isModified) { + $encryptedValue = $this->crypto->encrypt(json_encode($this->sessionValues), $this->passphrase); + $this->session->set(self::encryptedSessionName, $encryptedValue); + $this->isModified = false; + } + $this->session->close(); + } + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset): bool { + return $this->exists($offset); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) { + return $this->get($offset); + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) { + $this->remove($offset); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Session/CryptoWrapper.php b/docker/overlays/nextcloud/html/lib/private/Session/CryptoWrapper.php new file mode 100644 index 0000000..f7f26bb --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Session/CryptoWrapper.php @@ -0,0 +1,122 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Phil Davis + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Session; + +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; + +/** + * Class CryptoWrapper provides some rough basic level of additional security by + * storing the session data in an encrypted form. + * + * The content of the session is encrypted using another cookie sent by the browser. + * One should note that an adversary with access to the source code or the system + * memory is still able to read the original session ID from the users' request. + * This thus can not be considered a strong security measure one should consider + * it as an additional small security obfuscation layer to comply with compliance + * guidelines. + * + * TODO: Remove this in a future release with an approach such as + * https://github.com/owncloud/core/pull/17866 + * + * @package OC\Session + */ +class CryptoWrapper { + public const COOKIE_NAME = 'oc_sessionPassphrase'; + + /** @var IConfig */ + protected $config; + /** @var ISession */ + protected $session; + /** @var ICrypto */ + protected $crypto; + /** @var ISecureRandom */ + protected $random; + /** @var string */ + protected $passphrase; + + /** + * @param IConfig $config + * @param ICrypto $crypto + * @param ISecureRandom $random + * @param IRequest $request + */ + public function __construct(IConfig $config, + ICrypto $crypto, + ISecureRandom $random, + IRequest $request) { + $this->crypto = $crypto; + $this->config = $config; + $this->random = $random; + + if (!is_null($request->getCookie(self::COOKIE_NAME))) { + $this->passphrase = $request->getCookie(self::COOKIE_NAME); + } else { + $this->passphrase = $this->random->generate(128); + $secureCookie = $request->getServerProtocol() === 'https'; + // FIXME: Required for CI + if (!defined('PHPUNIT_RUN')) { + $webRoot = \OC::$WEBROOT; + if ($webRoot === '') { + $webRoot = '/'; + } + + if (PHP_VERSION_ID < 70300) { + setcookie(self::COOKIE_NAME, $this->passphrase, 0, $webRoot, '', $secureCookie, true); + } else { + setcookie( + self::COOKIE_NAME, + $this->passphrase, + [ + 'expires' => 0, + 'path' => $webRoot, + 'domain' => '', + 'secure' => $secureCookie, + 'httponly' => true, + 'samesite' => 'Lax', + ] + ); + } + } + } + } + + /** + * @param ISession $session + * @return ISession + */ + public function wrapSession(ISession $session) { + if (!($session instanceof CryptoSessionData)) { + return new CryptoSessionData($session, $this->crypto, $this->passphrase); + } + + return $session; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Session/Internal.php b/docker/overlays/nextcloud/html/lib/private/Session/Internal.php new file mode 100644 index 0000000..f08f4da --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Session/Internal.php @@ -0,0 +1,223 @@ + + * @author cetra3 + * @author Christoph Wurst + * @author Lukas Reschke + * @author MartB + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Session; + +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IProvider; +use OCP\Session\Exceptions\SessionNotAvailableException; + +/** + * Class Internal + * + * wrap php's internal session handling into the Session interface + * + * @package OC\Session + */ +class Internal extends Session { + /** + * @param string $name + * @throws \Exception + */ + public function __construct(string $name) { + set_error_handler([$this, 'trapError']); + $this->invoke('session_name', [$name]); + try { + $this->startSession(); + } catch (\Exception $e) { + setcookie($this->invoke('session_name'), '', -1, \OC::$WEBROOT ?: '/'); + } + restore_error_handler(); + if (!isset($_SESSION)) { + throw new \Exception('Failed to start session'); + } + } + + /** + * @param string $key + * @param integer $value + */ + public function set(string $key, $value) { + $this->validateSession(); + $_SESSION[$key] = $value; + } + + /** + * @param string $key + * @return mixed + */ + public function get(string $key) { + if (!$this->exists($key)) { + return null; + } + return $_SESSION[$key]; + } + + /** + * @param string $key + * @return bool + */ + public function exists(string $key): bool { + return isset($_SESSION[$key]); + } + + /** + * @param string $key + */ + public function remove(string $key) { + if (isset($_SESSION[$key])) { + unset($_SESSION[$key]); + } + } + + public function clear() { + $this->invoke('session_unset'); + $this->regenerateId(); + $this->startSession(true); + $_SESSION = []; + } + + public function close() { + $this->invoke('session_write_close'); + parent::close(); + } + + /** + * Wrapper around session_regenerate_id + * + * @param bool $deleteOldSession Whether to delete the old associated session file or not. + * @param bool $updateToken Wheater to update the associated auth token + * @return void + */ + public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { + $oldId = null; + + if ($updateToken) { + // Get the old id to update the token + try { + $oldId = $this->getId(); + } catch (SessionNotAvailableException $e) { + // We can't update a token if there is no previous id + $updateToken = false; + } + } + + try { + @session_regenerate_id($deleteOldSession); + } catch (\Error $e) { + $this->trapError($e->getCode(), $e->getMessage()); + } + + if ($updateToken) { + // Get the new id to update the token + $newId = $this->getId(); + + /** @var IProvider $tokenProvider */ + $tokenProvider = \OC::$server->query(IProvider::class); + + try { + $tokenProvider->renewSessionToken($oldId, $newId); + } catch (InvalidTokenException $e) { + // Just ignore + } + } + } + + /** + * Wrapper around session_id + * + * @return string + * @throws SessionNotAvailableException + * @since 9.1.0 + */ + public function getId(): string { + $id = $this->invoke('session_id', [], true); + if ($id === '') { + throw new SessionNotAvailableException(); + } + return $id; + } + + /** + * @throws \Exception + */ + public function reopen() { + throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); + } + + /** + * @param int $errorNumber + * @param string $errorString + * @throws \ErrorException + */ + public function trapError(int $errorNumber, string $errorString) { + throw new \ErrorException($errorString); + } + + /** + * @throws \Exception + */ + private function validateSession() { + if ($this->sessionClosed) { + throw new SessionNotAvailableException('Session has been closed - no further changes to the session are allowed'); + } + } + + /** + * @param string $functionName the full session_* function name + * @param array $parameters + * @param bool $silence whether to suppress warnings + * @throws \ErrorException via trapError + * @return mixed + */ + private function invoke(string $functionName, array $parameters = [], bool $silence = false) { + try { + if ($silence) { + return @call_user_func_array($functionName, $parameters); + } else { + return call_user_func_array($functionName, $parameters); + } + } catch (\Error $e) { + $this->trapError($e->getCode(), $e->getMessage()); + } + } + + private function startSession(bool $silence = false) { + if (PHP_VERSION_ID < 70300) { + $this->invoke('session_start', [], $silence); + } else { + $this->invoke('session_start', [['cookie_samesite' => 'Lax']], $silence); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Session/Memory.php b/docker/overlays/nextcloud/html/lib/private/Session/Memory.php new file mode 100644 index 0000000..abbf026 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Session/Memory.php @@ -0,0 +1,128 @@ + + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Session; + +use Exception; +use OCP\Session\Exceptions\SessionNotAvailableException; + +/** + * Class Internal + * + * store session data in an in-memory array, not persistent + * + * @package OC\Session + */ +class Memory extends Session { + protected $data; + + public function __construct(string $name) { + //no need to use $name since all data is already scoped to this instance + $this->data = []; + } + + /** + * @param string $key + * @param integer $value + */ + public function set(string $key, $value) { + $this->validateSession(); + $this->data[$key] = $value; + } + + /** + * @param string $key + * @return mixed + */ + public function get(string $key) { + if (!$this->exists($key)) { + return null; + } + return $this->data[$key]; + } + + /** + * @param string $key + * @return bool + */ + public function exists(string $key): bool { + return isset($this->data[$key]); + } + + /** + * @param string $key + */ + public function remove(string $key) { + $this->validateSession(); + unset($this->data[$key]); + } + + public function clear() { + $this->data = []; + } + + /** + * Stub since the session ID does not need to get regenerated for the cache + * + * @param bool $deleteOldSession + */ + public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { + } + + /** + * Wrapper around session_id + * + * @return string + * @throws SessionNotAvailableException + * @since 9.1.0 + */ + public function getId(): string { + throw new SessionNotAvailableException('Memory session does not have an ID'); + } + + /** + * Helper function for PHPUnit execution - don't use in non-test code + */ + public function reopen() { + $this->sessionClosed = false; + } + + /** + * In case the session has already been locked an exception will be thrown + * + * @throws Exception + */ + private function validateSession() { + if ($this->sessionClosed) { + throw new Exception('Session has been closed - no further changes to the session are allowed'); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Session/Session.php b/docker/overlays/nextcloud/html/lib/private/Session/Session.php new file mode 100644 index 0000000..fd6edd5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Session/Session.php @@ -0,0 +1,84 @@ + + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Session; + +use OCP\ISession; + +abstract class Session implements \ArrayAccess, ISession { + + /** + * @var bool + */ + protected $sessionClosed = false; + + /** + * $name serves as a namespace for the session keys + * + * @param string $name + */ + abstract public function __construct(string $name); + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset): bool { + return $this->exists($offset); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) { + return $this->get($offset); + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) { + $this->remove($offset); + } + + /** + * Close the session and release the lock + */ + public function close() { + $this->sessionClosed = true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Settings/Manager.php b/docker/overlays/nextcloud/html/lib/private/Settings/Manager.php new file mode 100644 index 0000000..65193e2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Settings/Manager.php @@ -0,0 +1,316 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author sualko + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Settings; + +use Closure; +use OCP\AppFramework\QueryException; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use OCP\Settings\IManager; +use OCP\Settings\ISection; +use OCP\Settings\ISettings; +use OCP\Settings\ISubAdminSettings; + +class Manager implements IManager { + + /** @var ILogger */ + private $log; + + /** @var IL10N */ + private $l; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IURLGenerator */ + private $url; + + /** @var IServerContainer */ + private $container; + + public function __construct( + ILogger $log, + IFactory $l10nFactory, + IURLGenerator $url, + IServerContainer $container + ) { + $this->log = $log; + $this->l10nFactory = $l10nFactory; + $this->url = $url; + $this->container = $container; + } + + /** @var array */ + protected $sectionClasses = []; + + /** @var array */ + protected $sections = []; + + /** + * @param string $type 'admin' or 'personal' + * @param string $section Class must implement OCP\Settings\ISection + * + * @return void + */ + public function registerSection(string $type, string $section) { + if (!isset($this->sectionClasses[$type])) { + $this->sectionClasses[$type] = []; + } + + $this->sectionClasses[$type][] = $section; + } + + /** + * @param string $type 'admin' or 'personal' + * + * @return ISection[] + */ + protected function getSections(string $type): array { + if (!isset($this->sections[$type])) { + $this->sections[$type] = []; + } + + if (!isset($this->sectionClasses[$type])) { + return $this->sections[$type]; + } + + foreach (array_unique($this->sectionClasses[$type]) as $index => $class) { + try { + /** @var ISection $section */ + $section = \OC::$server->query($class); + } catch (QueryException $e) { + $this->log->logException($e, ['level' => ILogger::INFO]); + continue; + } + + if (!$section instanceof ISection) { + $this->log->logException(new \InvalidArgumentException('Invalid settings section registered'), ['level' => ILogger::INFO]); + continue; + } + + $sectionID = $section->getID(); + + if ($sectionID !== 'connected-accounts' && isset($this->sections[$type][$sectionID])) { + $this->log->logException(new \InvalidArgumentException('Section with the same ID already registered: ' . $sectionID . ', class: ' . $class), ['level' => ILogger::INFO]); + continue; + } + + $this->sections[$type][$sectionID] = $section; + + unset($this->sectionClasses[$type][$index]); + } + + return $this->sections[$type]; + } + + /** @var array */ + protected $settingClasses = []; + + /** @var array */ + protected $settings = []; + + /** + * @param string $type 'admin' or 'personal' + * @param string $setting Class must implement OCP\Settings\ISetting + * + * @return void + */ + public function registerSetting(string $type, string $setting) { + $this->settingClasses[$setting] = $type; + } + + /** + * @param string $type 'admin' or 'personal' + * @param string $section + * @param Closure $filter optional filter to apply on all loaded ISettings + * + * @return ISettings[] + */ + protected function getSettings(string $type, string $section, Closure $filter = null): array { + if (!isset($this->settings[$type])) { + $this->settings[$type] = []; + } + if (!isset($this->settings[$type][$section])) { + $this->settings[$type][$section] = []; + } + + foreach ($this->settingClasses as $class => $settingsType) { + if ($type !== $settingsType) { + continue; + } + + try { + /** @var ISettings $setting */ + $setting = $this->container->query($class); + } catch (QueryException $e) { + $this->log->logException($e, ['level' => ILogger::INFO]); + continue; + } + + if (!$setting instanceof ISettings) { + $this->log->logException(new \InvalidArgumentException('Invalid settings setting registered (' . $class . ')'), ['level' => ILogger::INFO]); + continue; + } + + if ($filter !== null && !$filter($setting)) { + continue; + } + if ($setting->getSection() === null) { + continue; + } + + if (!isset($this->settings[$settingsType][$setting->getSection()])) { + $this->settings[$settingsType][$setting->getSection()] = []; + } + $this->settings[$settingsType][$setting->getSection()][] = $setting; + + unset($this->settingClasses[$class]); + } + + return $this->settings[$type][$section]; + } + + /** + * @inheritdoc + */ + public function getAdminSections(): array { + // built-in sections + $sections = []; + + $appSections = $this->getSections('admin'); + + foreach ($appSections as $section) { + /** @var ISection $section */ + if (!isset($sections[$section->getPriority()])) { + $sections[$section->getPriority()] = []; + } + + $sections[$section->getPriority()][] = $section; + } + + ksort($sections); + + return $sections; + } + + /** + * @inheritdoc + */ + public function getAdminSettings($section, bool $subAdminOnly = false): array { + if ($subAdminOnly) { + $subAdminSettingsFilter = function (ISettings $settings) { + return $settings instanceof ISubAdminSettings; + }; + $appSettings = $this->getSettings('admin', $section, $subAdminSettingsFilter); + } else { + $appSettings = $this->getSettings('admin', $section); + } + + $settings = []; + foreach ($appSettings as $setting) { + if (!isset($settings[$setting->getPriority()])) { + $settings[$setting->getPriority()] = []; + } + $settings[$setting->getPriority()][] = $setting; + } + + ksort($settings); + return $settings; + } + + /** + * @inheritdoc + */ + public function getPersonalSections(): array { + if ($this->l === null) { + $this->l = $this->l10nFactory->get('lib'); + } + + $sections = []; + + $legacyForms = \OC_App::getForms('personal'); + if ((!empty($legacyForms) && $this->hasLegacyPersonalSettingsToRender($legacyForms)) + || count($this->getPersonalSettings('additional')) > 1) { + $sections[98] = [new Section('additional', $this->l->t('Additional settings'), 0, $this->url->imagePath('core', 'actions/settings-dark.svg'))]; + } + + $appSections = $this->getSections('personal'); + + foreach ($appSections as $section) { + /** @var ISection $section */ + if (!isset($sections[$section->getPriority()])) { + $sections[$section->getPriority()] = []; + } + + $sections[$section->getPriority()][] = $section; + } + + ksort($sections); + + return $sections; + } + + /** + * @param string[] $forms + * + * @return bool + */ + private function hasLegacyPersonalSettingsToRender(array $forms): bool { + foreach ($forms as $form) { + if (trim($form) !== '') { + return true; + } + } + return false; + } + + /** + * @inheritdoc + */ + public function getPersonalSettings($section): array { + $settings = []; + $appSettings = $this->getSettings('personal', $section); + + foreach ($appSettings as $setting) { + if (!isset($settings[$setting->getPriority()])) { + $settings[$setting->getPriority()] = []; + } + $settings[$setting->getPriority()][] = $setting; + } + + ksort($settings); + return $settings; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Settings/Section.php b/docker/overlays/nextcloud/html/lib/private/Settings/Section.php new file mode 100644 index 0000000..5e0c772 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Settings/Section.php @@ -0,0 +1,94 @@ + + * + * @author Arthur Schiwon + * @author Joas Schilling + * @author Lukas Reschke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Settings; + +use OCP\Settings\IIconSection; + +class Section implements IIconSection { + /** @var string */ + private $id; + /** @var string */ + private $name; + /** @var int */ + private $priority; + /** @var string */ + private $icon; + + /** + * @param string $id + * @param string $name + * @param int $priority + * @param string $icon + */ + public function __construct($id, $name, $priority, $icon = '') { + $this->id = $id; + $this->name = $name; + $this->priority = $priority; + $this->icon = $icon; + } + + /** + * returns the ID of the section. It is supposed to be a lower case string, + * e.g. 'ldap' + * + * @returns string + */ + public function getID() { + return $this->id; + } + + /** + * returns the translated name as it should be displayed, e.g. 'LDAP / AD + * integration'. Use the L10N service to translate it. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the settings navigation. The sections are arranged in ascending order of + * the priority values. It is required to return a value between 0 and 99. + * + * E.g.: 70 + */ + public function getPriority() { + return $this->priority; + } + + /** + * returns the relative path to an 16*16 icon describing the section. + * e.g. '/core/img/places/files.svg' + * + * @returns string + * @since 12 + */ + public function getIcon() { + return $this->icon; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup.php b/docker/overlays/nextcloud/html/lib/private/Setup.php new file mode 100644 index 0000000..369447d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup.php @@ -0,0 +1,613 @@ + + * @author Bart Visscher + * @author Bernhard Posselt + * @author Bjoern Schiessle + * @author Brice Maron + * @author Christoph Wurst + * @author Dan Callahan + * @author Daniel Kesselberg + * @author François Kubler + * @author Frank Isemann + * @author Jakob Sack + * @author Joas Schilling + * @author Julius Härtl + * @author KB7777 + * @author Kevin Lanni + * @author Lukas Reschke + * @author MichaIng <28480705+MichaIng@users.noreply.github.com> + * @author MichaIng + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sean Comeau + * @author Serge Martin + * @author Simounet + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use bantu\IniGetWrapper\IniGetWrapper; +use Exception; +use InvalidArgumentException; +use OC\App\AppStore\Bundles\BundleFetcher; +use OC\Authentication\Token\DefaultTokenCleanupJob; +use OC\Authentication\Token\DefaultTokenProvider; +use OC\Log\Rotate; +use OC\Preview\BackgroundCleanupJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Defaults; +use OCP\IGroup; +use OCP\IL10N; +use OCP\ILogger; +use OCP\Security\ISecureRandom; + +class Setup { + /** @var SystemConfig */ + protected $config; + /** @var IniGetWrapper */ + protected $iniWrapper; + /** @var IL10N */ + protected $l10n; + /** @var Defaults */ + protected $defaults; + /** @var ILogger */ + protected $logger; + /** @var ISecureRandom */ + protected $random; + /** @var Installer */ + protected $installer; + + /** + * @param SystemConfig $config + * @param IniGetWrapper $iniWrapper + * @param IL10N $l10n + * @param Defaults $defaults + * @param ILogger $logger + * @param ISecureRandom $random + * @param Installer $installer + */ + public function __construct( + SystemConfig $config, + IniGetWrapper $iniWrapper, + IL10N $l10n, + Defaults $defaults, + ILogger $logger, + ISecureRandom $random, + Installer $installer + ) { + $this->config = $config; + $this->iniWrapper = $iniWrapper; + $this->l10n = $l10n; + $this->defaults = $defaults; + $this->logger = $logger; + $this->random = $random; + $this->installer = $installer; + } + + protected static $dbSetupClasses = [ + 'mysql' => \OC\Setup\MySQL::class, + 'pgsql' => \OC\Setup\PostgreSQL::class, + 'oci' => \OC\Setup\OCI::class, + 'sqlite' => \OC\Setup\Sqlite::class, + 'sqlite3' => \OC\Setup\Sqlite::class, + ]; + + /** + * Wrapper around the "class_exists" PHP function to be able to mock it + * + * @param string $name + * @return bool + */ + protected function class_exists($name) { + return class_exists($name); + } + + /** + * Wrapper around the "is_callable" PHP function to be able to mock it + * + * @param string $name + * @return bool + */ + protected function is_callable($name) { + return is_callable($name); + } + + /** + * Wrapper around \PDO::getAvailableDrivers + * + * @return array + */ + protected function getAvailableDbDriversForPdo() { + return \PDO::getAvailableDrivers(); + } + + /** + * Get the available and supported databases of this instance + * + * @param bool $allowAllDatabases + * @return array + * @throws Exception + */ + public function getSupportedDatabases($allowAllDatabases = false) { + $availableDatabases = [ + 'sqlite' => [ + 'type' => 'pdo', + 'call' => 'sqlite', + 'name' => 'SQLite', + ], + 'mysql' => [ + 'type' => 'pdo', + 'call' => 'mysql', + 'name' => 'MySQL/MariaDB', + ], + 'pgsql' => [ + 'type' => 'pdo', + 'call' => 'pgsql', + 'name' => 'PostgreSQL', + ], + 'oci' => [ + 'type' => 'function', + 'call' => 'oci_connect', + 'name' => 'Oracle', + ], + ]; + if ($allowAllDatabases) { + $configuredDatabases = array_keys($availableDatabases); + } else { + $configuredDatabases = $this->config->getValue('supportedDatabases', + ['sqlite', 'mysql', 'pgsql']); + } + if (!is_array($configuredDatabases)) { + throw new Exception('Supported databases are not properly configured.'); + } + + $supportedDatabases = []; + + foreach ($configuredDatabases as $database) { + if (array_key_exists($database, $availableDatabases)) { + $working = false; + $type = $availableDatabases[$database]['type']; + $call = $availableDatabases[$database]['call']; + + if ($type === 'function') { + $working = $this->is_callable($call); + } elseif ($type === 'pdo') { + $working = in_array($call, $this->getAvailableDbDriversForPdo(), true); + } + if ($working) { + $supportedDatabases[$database] = $availableDatabases[$database]['name']; + } + } + } + + return $supportedDatabases; + } + + /** + * Gathers system information like database type and does + * a few system checks. + * + * @return array of system info, including an "errors" value + * in case of errors/warnings + */ + public function getSystemInfo($allowAllDatabases = false) { + $databases = $this->getSupportedDatabases($allowAllDatabases); + + $dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT . '/data'); + + $errors = []; + + // Create data directory to test whether the .htaccess works + // Notice that this is not necessarily the same data directory as the one + // that will effectively be used. + if (!file_exists($dataDir)) { + @mkdir($dataDir); + } + $htAccessWorking = true; + if (is_dir($dataDir) && is_writable($dataDir)) { + // Protect data directory here, so we can test if the protection is working + self::protectDataDirectory(); + + try { + $util = new \OC_Util(); + $htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig()); + } catch (\OC\HintException $e) { + $errors[] = [ + 'error' => $e->getMessage(), + 'hint' => $e->getHint(), + ]; + $htAccessWorking = false; + } + } + + if (\OC_Util::runningOnMac()) { + $errors[] = [ + 'error' => $this->l10n->t( + 'Mac OS X is not supported and %s will not work properly on this platform. ' . + 'Use it at your own risk! ', + [$this->defaults->getName()] + ), + 'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.'), + ]; + } + + if ($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) { + $errors[] = [ + 'error' => $this->l10n->t( + 'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' . + 'This will lead to problems with files over 4 GB and is highly discouraged.', + [$this->defaults->getName()] + ), + 'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'), + ]; + } + + return [ + 'hasSQLite' => isset($databases['sqlite']), + 'hasMySQL' => isset($databases['mysql']), + 'hasPostgreSQL' => isset($databases['pgsql']), + 'hasOracle' => isset($databases['oci']), + 'databases' => $databases, + 'directory' => $dataDir, + 'htaccessWorking' => $htAccessWorking, + 'errors' => $errors, + ]; + } + + /** + * @param $options + * @return array + */ + public function install($options) { + $l = $this->l10n; + + $error = []; + $dbType = $options['dbtype']; + + if (empty($options['adminlogin'])) { + $error[] = $l->t('Set an admin username.'); + } + if (empty($options['adminpass'])) { + $error[] = $l->t('Set an admin password.'); + } + if (empty($options['directory'])) { + $options['directory'] = \OC::$SERVERROOT . "/data"; + } + + if (!isset(self::$dbSetupClasses[$dbType])) { + $dbType = 'sqlite'; + } + + $username = htmlspecialchars_decode($options['adminlogin']); + $password = htmlspecialchars_decode($options['adminpass']); + $dataDir = htmlspecialchars_decode($options['directory']); + + $class = self::$dbSetupClasses[$dbType]; + /** @var \OC\Setup\AbstractDatabase $dbSetup */ + $dbSetup = new $class($l, $this->config, $this->logger, $this->random); + $error = array_merge($error, $dbSetup->validate($options)); + + // validate the data directory + if ((!is_dir($dataDir) && !mkdir($dataDir)) || !is_writable($dataDir)) { + $error[] = $l->t("Can't create or write into the data directory %s", [$dataDir]); + } + + if (!empty($error)) { + return $error; + } + + $request = \OC::$server->getRequest(); + + //no errors, good + if (isset($options['trusted_domains']) + && is_array($options['trusted_domains'])) { + $trustedDomains = $options['trusted_domains']; + } else { + $trustedDomains = [$request->getInsecureServerHost()]; + } + + //use sqlite3 when available, otherwise sqlite2 will be used. + if ($dbType === 'sqlite' && class_exists('SQLite3')) { + $dbType = 'sqlite3'; + } + + //generate a random salt that is used to salt the local user passwords + $salt = $this->random->generate(30); + // generate a secret + $secret = $this->random->generate(48); + + //write the config file + $newConfigValues = [ + 'passwordsalt' => $salt, + 'secret' => $secret, + 'trusted_domains' => $trustedDomains, + 'datadirectory' => $dataDir, + 'dbtype' => $dbType, + 'version' => implode('.', \OCP\Util::getVersion()), + ]; + + if ($this->config->getValue('overwrite.cli.url', null) === null) { + $newConfigValues['overwrite.cli.url'] = $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT; + } + + $this->config->setValues($newConfigValues); + + $dbSetup->initialize($options); + try { + $dbSetup->setupDatabase($username); + } catch (\OC\DatabaseSetupException $e) { + $error[] = [ + 'error' => $e->getMessage(), + 'hint' => $e->getHint(), + ]; + return $error; + } catch (Exception $e) { + $error[] = [ + 'error' => 'Error while trying to create admin user: ' . $e->getMessage(), + 'hint' => '', + ]; + return $error; + } + try { + // apply necessary migrations + $dbSetup->runMigrations(); + } catch (Exception $e) { + $error[] = [ + 'error' => 'Error while trying to initialise the database: ' . $e->getMessage(), + 'hint' => '', + ]; + return $error; + } + + //create the user and group + $user = null; + try { + $user = \OC::$server->getUserManager()->createUser($username, $password); + if (!$user) { + $error[] = "User <$username> could not be created."; + } + } catch (Exception $exception) { + $error[] = $exception->getMessage(); + } + + if (empty($error)) { + $config = \OC::$server->getConfig(); + $config->setAppValue('core', 'installedat', microtime(true)); + $config->setAppValue('core', 'lastupdatedat', microtime(true)); + $config->setAppValue('core', 'vendor', $this->getVendor()); + + $group = \OC::$server->getGroupManager()->createGroup('admin'); + if ($group instanceof IGroup) { + $group->addUser($user); + } + + // Install shipped apps and specified app bundles + Installer::installShippedApps(); + $bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib')); + $defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle(); + foreach ($defaultInstallationBundles as $bundle) { + try { + $this->installer->installAppBundle($bundle); + } catch (Exception $e) { + } + } + + // create empty file in data dir, so we can later find + // out that this is indeed an ownCloud data directory + file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', ''); + + // Update .htaccess files + self::updateHtaccess(); + self::protectDataDirectory(); + + self::installBackgroundJobs(); + + //and we are done + $config->setSystemValue('installed', true); + + // Create a session token for the newly created user + // The token provider requires a working db, so it's not injected on setup + /* @var $userSession User\Session */ + $userSession = \OC::$server->getUserSession(); + $defaultTokenProvider = \OC::$server->query(DefaultTokenProvider::class); + $userSession->setTokenProvider($defaultTokenProvider); + $userSession->login($username, $password); + $userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password); + + $session = $userSession->getSession(); + $session->set('last-password-confirm', \OC::$server->query(ITimeFactory::class)->getTime()); + + // Set email for admin + if (!empty($options['adminemail'])) { + $config->setUserValue($user->getUID(), 'settings', 'email', $options['adminemail']); + } + } + + return $error; + } + + public static function installBackgroundJobs() { + $jobList = \OC::$server->getJobList(); + $jobList->add(DefaultTokenCleanupJob::class); + $jobList->add(Rotate::class); + $jobList->add(BackgroundCleanupJob::class); + } + + /** + * @return string Absolute path to htaccess + */ + private function pathToHtaccess() { + return \OC::$SERVERROOT . '/.htaccess'; + } + + /** + * Find webroot from config + * + * @param SystemConfig $config + * @return string + * @throws InvalidArgumentException when invalid value for overwrite.cli.url + */ + private static function findWebRoot(SystemConfig $config): string { + // For CLI read the value from overwrite.cli.url + if (\OC::$CLI) { + $webRoot = $config->getValue('overwrite.cli.url', ''); + if ($webRoot === '') { + throw new InvalidArgumentException('overwrite.cli.url is empty'); + } + if (!filter_var($webRoot, FILTER_VALIDATE_URL)) { + throw new InvalidArgumentException('invalid value for overwrite.cli.url'); + } + $webRoot = rtrim(parse_url($webRoot, PHP_URL_PATH), '/'); + } else { + $webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/'; + } + + return $webRoot; + } + + /** + * Append the correct ErrorDocument path for Apache hosts + * + * @return bool True when success, False otherwise + * @throws \OCP\AppFramework\QueryException + */ + public static function updateHtaccess() { + $config = \OC::$server->getSystemConfig(); + + try { + $webRoot = self::findWebRoot($config); + } catch (InvalidArgumentException $e) { + return false; + } + + $setupHelper = new \OC\Setup( + $config, + \OC::$server->get(IniGetWrapper::class), + \OC::$server->getL10N('lib'), + \OC::$server->query(Defaults::class), + \OC::$server->getLogger(), + \OC::$server->getSecureRandom(), + \OC::$server->query(Installer::class) + ); + + $htaccessContent = file_get_contents($setupHelper->pathToHtaccess()); + $content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n"; + $htaccessContent = explode($content, $htaccessContent, 2)[0]; + + //custom 403 error page + $content .= "\nErrorDocument 403 " . $webRoot . '/'; + + //custom 404 error page + $content .= "\nErrorDocument 404 " . $webRoot . '/'; + + // Add rewrite rules if the RewriteBase is configured + $rewriteBase = $config->getValue('htaccess.RewriteBase', ''); + if ($rewriteBase !== '') { + $content .= "\n"; + $content .= "\n Options -MultiViews"; + $content .= "\n RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]"; + $content .= "\n RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav)$"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !core/img/manifest.json$"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/remote.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/public.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/cron.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/status.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/robots.txt"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/updater/"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocs-provider/"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocm-provider/"; + $content .= "\n RewriteCond %{REQUEST_URI} !^/\\.well-known/(acme-challenge|pki-validation)/.*"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/richdocumentscode(_arm64)?/proxy.php$"; + $content .= "\n RewriteRule . index.php [PT,E=PATH_INFO:$1]"; + $content .= "\n RewriteBase " . $rewriteBase; + $content .= "\n "; + $content .= "\n SetEnv front_controller_active true"; + $content .= "\n "; + $content .= "\n DirectorySlash off"; + $content .= "\n "; + $content .= "\n "; + $content .= "\n"; + } + + if ($content !== '') { + //suppress errors in case we don't have permissions for it + return (bool)@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent . $content . "\n"); + } + + return false; + } + + public static function protectDataDirectory() { + //Require all denied + $now = date('Y-m-d H:i:s'); + $content = "# Generated by Nextcloud on $now\n"; + $content .= "# Section for Apache 2.4 to 2.6\n"; + $content .= "\n"; + $content .= " Require all denied\n"; + $content .= "\n"; + $content .= "\n"; + $content .= " Order Allow,Deny\n"; + $content .= " Deny from all\n"; + $content .= " Satisfy All\n"; + $content .= "\n\n"; + $content .= "# Section for Apache 2.2\n"; + $content .= "\n"; + $content .= " \n"; + $content .= " \n"; + $content .= " Order Allow,Deny\n"; + $content .= " Deny from all\n"; + $content .= " \n"; + $content .= " Satisfy All\n"; + $content .= " \n"; + $content .= "\n\n"; + $content .= "# Section for Apache 2.2 to 2.6\n"; + $content .= "\n"; + $content .= " IndexIgnore *\n"; + $content .= ""; + + $baseDir = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data'); + file_put_contents($baseDir . '/.htaccess', $content); + file_put_contents($baseDir . '/index.html', ''); + } + + /** + * Return vendor from which this version was published + * + * @return string Get the vendor + * + * Copy of \OC\Updater::getVendor() + */ + private function getVendor() { + // this should really be a JSON file + require \OC::$SERVERROOT . '/version.php'; + /** @var string $vendor */ + return (string)$vendor; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup/AbstractDatabase.php b/docker/overlays/nextcloud/html/lib/private/Setup/AbstractDatabase.php new file mode 100644 index 0000000..b2c290e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup/AbstractDatabase.php @@ -0,0 +1,155 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Pulzer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Setup; + +use OC\DB\ConnectionFactory; +use OC\DB\MigrationService; +use OC\SystemConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\Security\ISecureRandom; + +abstract class AbstractDatabase { + + /** @var IL10N */ + protected $trans; + /** @var string */ + protected $dbUser; + /** @var string */ + protected $dbPassword; + /** @var string */ + protected $dbName; + /** @var string */ + protected $dbHost; + /** @var string */ + protected $dbPort; + /** @var string */ + protected $tablePrefix; + /** @var SystemConfig */ + protected $config; + /** @var ILogger */ + protected $logger; + /** @var ISecureRandom */ + protected $random; + + public function __construct(IL10N $trans, SystemConfig $config, ILogger $logger, ISecureRandom $random) { + $this->trans = $trans; + $this->config = $config; + $this->logger = $logger; + $this->random = $random; + } + + public function validate($config) { + $errors = []; + if (empty($config['dbuser']) && empty($config['dbname'])) { + $errors[] = $this->trans->t("%s enter the database username and name.", [$this->dbprettyname]); + } elseif (empty($config['dbuser'])) { + $errors[] = $this->trans->t("%s enter the database username.", [$this->dbprettyname]); + } elseif (empty($config['dbname'])) { + $errors[] = $this->trans->t("%s enter the database name.", [$this->dbprettyname]); + } + if (substr_count($config['dbname'], '.') >= 1) { + $errors[] = $this->trans->t("%s you may not use dots in the database name", [$this->dbprettyname]); + } + return $errors; + } + + public function initialize($config) { + $dbUser = $config['dbuser']; + $dbPass = $config['dbpass']; + $dbName = $config['dbname']; + $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost'; + $dbPort = !empty($config['dbport']) ? $config['dbport'] : ''; + $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; + + $this->config->setValues([ + 'dbname' => $dbName, + 'dbhost' => $dbHost, + 'dbport' => $dbPort, + 'dbtableprefix' => $dbTablePrefix, + ]); + + $this->dbUser = $dbUser; + $this->dbPassword = $dbPass; + $this->dbName = $dbName; + $this->dbHost = $dbHost; + $this->dbPort = $dbPort; + $this->tablePrefix = $dbTablePrefix; + } + + /** + * @param array $configOverwrite + * @return \OC\DB\Connection + */ + protected function connect(array $configOverwrite = []) { + $connectionParams = [ + 'host' => $this->dbHost, + 'user' => $this->dbUser, + 'password' => $this->dbPassword, + 'tablePrefix' => $this->tablePrefix, + 'dbname' => $this->dbName + ]; + + // adding port support through installer + if (!empty($this->dbPort)) { + if (ctype_digit($this->dbPort)) { + $connectionParams['port'] = $this->dbPort; + } else { + $connectionParams['unix_socket'] = $this->dbPort; + } + } elseif (strpos($this->dbHost, ':')) { + // Host variable may carry a port or socket. + list($host, $portOrSocket) = explode(':', $this->dbHost, 2); + if (ctype_digit($portOrSocket)) { + $connectionParams['port'] = $portOrSocket; + } else { + $connectionParams['unix_socket'] = $portOrSocket; + } + $connectionParams['host'] = $host; + } + + $connectionParams = array_merge($connectionParams, $configOverwrite); + $cf = new ConnectionFactory($this->config); + return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams); + } + + /** + * @param string $userName + */ + abstract public function setupDatabase($userName); + + public function runMigrations() { + if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) { + return; + } + $ms = new MigrationService('core', \OC::$server->getDatabaseConnection()); + $ms->migrate(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup/MySQL.php b/docker/overlays/nextcloud/html/lib/private/Setup/MySQL.php new file mode 100644 index 0000000..6a5513e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup/MySQL.php @@ -0,0 +1,197 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Hemanth Kumar Veeranki + * @author Joas Schilling + * @author Michael Göhler + * @author Morris Jobke + * @author Oliver Salzburg + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Setup; + +use OC\DB\MySqlTools; +use OCP\IDBConnection; +use OCP\ILogger; +use Doctrine\DBAL\Platforms\MySQL80Platform; + +class MySQL extends AbstractDatabase { + public $dbprettyname = 'MySQL/MariaDB'; + + public function setupDatabase($username) { + //check if the database user has admin right + $connection = $this->connect(['dbname' => null]); + + // detect mb4 + $tools = new MySqlTools(); + if ($tools->supports4ByteCharset($connection)) { + $this->config->setValue('mysql.utf8mb4', true); + $connection = $this->connect(['dbname' => null]); + } + + $this->createSpecificUser($username, $connection); + + //create the database + $this->createDatabase($connection); + + //fill the database if needed + $query='select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; + $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']); + + $connection->close(); + $connection = $this->connect(); + try { + $connection->connect(); + } catch (\Exception $e) { + $this->logger->logException($e); + throw new \OC\DatabaseSetupException($this->trans->t('MySQL username and/or password not valid'), + $this->trans->t('You need to enter details of an existing account.')); + } + } + + /** + * @param \OC\DB\Connection $connection + */ + private function createDatabase($connection) { + try { + $name = $this->dbName; + $user = $this->dbUser; + //we can't use OC_DB functions here because we need to connect as the administrative user. + $characterSet = $this->config->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; + $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE ${characterSet}_bin;"; + $connection->executeUpdate($query); + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'message' => 'Database creation failed.', + 'level' => ILogger::ERROR, + 'app' => 'mysql.setup', + ]); + return; + } + + try { + //this query will fail if there aren't the right permissions, ignore the error + $query="GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `$name` . * TO '$user'"; + $connection->executeUpdate($query); + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'message' => 'Could not automatically grant privileges, this can be ignored if database user already had privileges.', + 'level' => ILogger::DEBUG, + 'app' => 'mysql.setup', + ]); + } + } + + /** + * @param IDBConnection $connection + * @throws \OC\DatabaseSetupException + */ + private function createDBUser($connection) { + try { + $name = $this->dbUser; + $password = $this->dbPassword; + // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one, + // the anonymous user would take precedence when there is one. + + if ($connection->getDatabasePlatform() instanceof Mysql80Platform) { + $query = "CREATE USER '$name'@'localhost' IDENTIFIED WITH mysql_native_password BY '$password'"; + $connection->executeUpdate($query); + $query = "CREATE USER '$name'@'%' IDENTIFIED WITH mysql_native_password BY '$password'"; + $connection->executeUpdate($query); + } else { + $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'"; + $connection->executeUpdate($query); + $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'"; + $connection->executeUpdate($query); + } + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'message' => 'Database user creation failed.', + 'level' => ILogger::ERROR, + 'app' => 'mysql.setup', + ]); + } + } + + /** + * @param $username + * @param IDBConnection $connection + * @return array + */ + private function createSpecificUser($username, $connection) { + try { + //user already specified in config + $oldUser = $this->config->getValue('dbuser', false); + + //we don't have a dbuser specified in config + if ($this->dbUser !== $oldUser) { + //add prefix to the admin username to prevent collisions + $adminUser = substr('oc_' . $username, 0, 16); + + $i = 1; + while (true) { + //this should be enough to check for admin rights in mysql + $query = 'SELECT user FROM mysql.user WHERE user=?'; + $result = $connection->executeQuery($query, [$adminUser]); + + //current dbuser has admin rights + if ($result) { + $data = $result->fetchAll(); + //new dbuser does not exist + if (count($data) === 0) { + //use the admin login data for the new database user + $this->dbUser = $adminUser; + + //create a random password so we don't need to store the admin password in the config file + $this->dbPassword = $this->random->generate(30); + + $this->createDBUser($connection); + + break; + } else { + //repeat with different username + $length = strlen((string)$i); + $adminUser = substr('oc_' . $username, 0, 16 - $length) . $i; + $i++; + } + } else { + break; + } + } + } + } catch (\Exception $ex) { + $this->logger->logException($ex, [ + 'message' => 'Can not create a new MySQL user, will continue with the provided user.', + 'level' => ILogger::INFO, + 'app' => 'mysql.setup', + ]); + } + + $this->config->setValues([ + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, + ]); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup/OCI.php b/docker/overlays/nextcloud/html/lib/private/Setup/OCI.php new file mode 100644 index 0000000..1d7fe27 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup/OCI.php @@ -0,0 +1,111 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Setup; + +class OCI extends AbstractDatabase { + public $dbprettyname = 'Oracle'; + + protected $dbtablespace; + + public function initialize($config) { + parent::initialize($config); + if (array_key_exists('dbtablespace', $config)) { + $this->dbtablespace = $config['dbtablespace']; + } else { + $this->dbtablespace = 'USERS'; + } + // allow empty hostname for oracle + $this->dbHost = $config['dbhost']; + + $this->config->setValues([ + 'dbhost' => $this->dbHost, + 'dbtablespace' => $this->dbtablespace, + ]); + } + + public function validate($config) { + $errors = []; + if (empty($config['dbuser']) && empty($config['dbname'])) { + $errors[] = $this->trans->t("%s enter the database username and name.", [$this->dbprettyname]); + } elseif (empty($config['dbuser'])) { + $errors[] = $this->trans->t("%s enter the database username.", [$this->dbprettyname]); + } elseif (empty($config['dbname'])) { + $errors[] = $this->trans->t("%s enter the database name.", [$this->dbprettyname]); + } + return $errors; + } + + public function setupDatabase($username) { + try { + $this->connect(); + } catch (\Exception $e) { + $errorMessage = $this->getLastError(); + if ($errorMessage) { + throw new \OC\DatabaseSetupException($this->trans->t('Oracle connection could not be established'), + $errorMessage . ' Check environment: ORACLE_HOME=' . getenv('ORACLE_HOME') + . ' ORACLE_SID=' . getenv('ORACLE_SID') + . ' LD_LIBRARY_PATH=' . getenv('LD_LIBRARY_PATH') + . ' NLS_LANG=' . getenv('NLS_LANG') + . ' tnsnames.ora is ' . (is_readable(getenv('ORACLE_HOME') . '/network/admin/tnsnames.ora') ? '' : 'not ') . 'readable'); + } + throw new \OC\DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'), + 'Check environment: ORACLE_HOME=' . getenv('ORACLE_HOME') + . ' ORACLE_SID=' . getenv('ORACLE_SID') + . ' LD_LIBRARY_PATH=' . getenv('LD_LIBRARY_PATH') + . ' NLS_LANG=' . getenv('NLS_LANG') + . ' tnsnames.ora is ' . (is_readable(getenv('ORACLE_HOME') . '/network/admin/tnsnames.ora') ? '' : 'not ') . 'readable'); + } + + $this->config->setValues([ + 'dbuser' => $this->dbUser, + 'dbname' => $this->dbName, + 'dbpassword' => $this->dbPassword, + ]); + } + + /** + * @param resource $connection + * @return string + */ + protected function getLastError($connection = null) { + if ($connection) { + $error = oci_error($connection); + } else { + $error = oci_error(); + } + foreach (['message', 'code'] as $key) { + if (isset($error[$key])) { + return $error[$key]; + } + } + return ''; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup/PostgreSQL.php b/docker/overlays/nextcloud/html/lib/private/Setup/PostgreSQL.php new file mode 100644 index 0000000..3d7a0b2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup/PostgreSQL.php @@ -0,0 +1,168 @@ + + * @author eduardo + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vitor Mattos + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Setup; + +use OC\DatabaseException; +use OC\DB\QueryBuilder\Literal; +use OCP\IDBConnection; + +class PostgreSQL extends AbstractDatabase { + public $dbprettyname = 'PostgreSQL'; + + /** + * @param string $username + * @throws \OC\DatabaseSetupException + */ + public function setupDatabase($username) { + try { + $connection = $this->connect([ + 'dbname' => 'postgres' + ]); + //check for roles creation rights in postgresql + $builder = $connection->getQueryBuilder(); + $builder->automaticTablePrefix(false); + $query = $builder + ->select('rolname') + ->from('pg_roles') + ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE'))) + ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); + + try { + $result = $query->execute(); + $canCreateRoles = $result->rowCount() > 0; + } catch (DatabaseException $e) { + $canCreateRoles = false; + } + + if ($canCreateRoles) { + //use the admin login data for the new database user + + //add prefix to the postgresql user name to prevent collisions + $this->dbUser = 'oc_' . strtolower($username); + //create a new password so we don't need to store the admin config in the config file + $this->dbPassword = \OC::$server->getSecureRandom()->generate(30, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS); + + $this->createDBUser($connection); + } + + $this->config->setValues([ + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, + ]); + + //create the database + $this->createDatabase($connection); + // the connection to dbname=postgres is not needed anymore + $connection->close(); + } catch (\Exception $e) { + $this->logger->logException($e); + $this->logger->warning('Error trying to connect as "postgres", assuming database is setup and tables need to be created'); + $this->config->setValues([ + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, + ]); + } + + // connect to the database (dbname=$this->dbname) and check if it needs to be filled + $this->dbUser = $this->config->getValue('dbuser'); + $this->dbPassword = $this->config->getValue('dbpassword'); + $connection = $this->connect(); + try { + $connection->connect(); + } catch (\Exception $e) { + $this->logger->logException($e); + throw new \OC\DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'), + $this->trans->t('You need to enter details of an existing account.')); + } + } + + private function createDatabase(IDBConnection $connection) { + if (!$this->databaseExists($connection)) { + //The database does not exists... let's create it + $query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER " . addslashes($this->dbUser)); + try { + $query->execute(); + } catch (DatabaseException $e) { + $this->logger->error('Error while trying to create database'); + $this->logger->logException($e); + } + } else { + $query = $connection->prepare("REVOKE ALL PRIVILEGES ON DATABASE " . addslashes($this->dbName) . " FROM PUBLIC"); + try { + $query->execute(); + } catch (DatabaseException $e) { + $this->logger->error('Error while trying to restrict database permissions'); + $this->logger->logException($e); + } + } + } + + private function userExists(IDBConnection $connection) { + $builder = $connection->getQueryBuilder(); + $builder->automaticTablePrefix(false); + $query = $builder->select('*') + ->from('pg_roles') + ->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); + $result = $query->execute(); + return $result->rowCount() > 0; + } + + private function databaseExists(IDBConnection $connection) { + $builder = $connection->getQueryBuilder(); + $builder->automaticTablePrefix(false); + $query = $builder->select('datname') + ->from('pg_database') + ->where($builder->expr()->eq('datname', $builder->createNamedParameter($this->dbName))); + $result = $query->execute(); + return $result->rowCount() > 0; + } + + private function createDBUser(IDBConnection $connection) { + $dbUser = $this->dbUser; + try { + $i = 1; + while ($this->userExists($connection)) { + $i++; + $this->dbUser = $dbUser . $i; + } + + // create the user + $query = $connection->prepare("CREATE USER " . addslashes($this->dbUser) . " CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'"); + $query->execute(); + if ($this->databaseExists($connection)) { + $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO '.addslashes($this->dbUser)); + $query->execute(); + } + } catch (DatabaseException $e) { + $this->logger->error('Error while trying to create database user'); + $this->logger->logException($e); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Setup/Sqlite.php b/docker/overlays/nextcloud/html/lib/private/Setup/Sqlite.php new file mode 100644 index 0000000..b7d9857 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Setup/Sqlite.php @@ -0,0 +1,76 @@ + + * @author Christoph Wurst + * @author Daniel Kesselberg + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Setup; + +use OC\DB\ConnectionFactory; + +class Sqlite extends AbstractDatabase { + public $dbprettyname = 'Sqlite'; + + public function validate($config) { + return []; + } + + public function initialize($config) { + /* + * Web: When using web based installer its not possible to set dbname + * or dbtableprefix. Defaults used from ConnectionFactory and dbtype = 'sqlite' + * is written to config.php. + * + * Cli: When --database-name or --database-table-prefix empty or default + * dbtype = 'sqlite' is written to config.php. If you choose a value different + * from default these values are written to config.php. This is required because + * in connection factory configuration is obtained from config.php. + */ + + $this->dbName = empty($config['dbname']) + ? ConnectionFactory::DEFAULT_DBNAME + : $config['dbname']; + + $this->tablePrefix = empty($config['dbtableprefix']) + ? ConnectionFactory::DEFAULT_DBTABLEPREFIX + : $config['dbtableprefix']; + + if ($this->dbName !== ConnectionFactory::DEFAULT_DBNAME) { + $this->config->setValue('dbname', $this->dbName); + } + + if ($this->tablePrefix !== ConnectionFactory::DEFAULT_DBTABLEPREFIX) { + $this->config->setValue('dbtableprefix', $this->tablePrefix); + } + } + + public function setupDatabase($username) { + $datadir = $this->config->getValue( + 'datadirectory', + \OC::$SERVERROOT . '/data' + ); + + $sqliteFile = $datadir . '/' . $this->dbName . 'db'; + if (file_exists($sqliteFile)) { + unlink($sqliteFile); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share/Constants.php b/docker/overlays/nextcloud/html/lib/private/Share/Constants.php new file mode 100644 index 0000000..2310859 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share/Constants.php @@ -0,0 +1,87 @@ + + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Joas Schilling + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share; + +use OCP\Share\IShare; + +class Constants { + + /** + * @deprecated 17.0.0 - use IShare::TYPE_USER instead + */ + public const SHARE_TYPE_USER = 0; + /** + * @deprecated 17.0.0 - use IShare::TYPE_GROUP instead + */ + public const SHARE_TYPE_GROUP = 1; + // const SHARE_TYPE_USERGROUP = 2; // Internal type used by DefaultShareProvider + /** + * @deprecated 17.0.0 - use IShare::TYPE_LINK instead + */ + public const SHARE_TYPE_LINK = 3; + /** + * @deprecated 17.0.0 - use IShare::TYPE_EMAIL instead + */ + public const SHARE_TYPE_EMAIL = 4; + public const SHARE_TYPE_CONTACT = 5; // ToDo Check if it is still in use otherwise remove it + /** + * @deprecated 17.0.0 - use IShare::TYPE_REMOTE instead + */ + public const SHARE_TYPE_REMOTE = 6; + /** + * @deprecated 17.0.0 - use IShare::TYPE_CIRCLE instead + */ + public const SHARE_TYPE_CIRCLE = 7; + /** + * @deprecated 17.0.0 - use IShare::TYPE_GUEST instead + */ + public const SHARE_TYPE_GUEST = 8; + /** + * @deprecated 17.0.0 - use IShare::REMOTE_GROUP instead + */ + public const SHARE_TYPE_REMOTE_GROUP = 9; + /** + * @deprecated 17.0.0 - use IShare::TYPE_ROOM instead + */ + public const SHARE_TYPE_ROOM = 10; + // const SHARE_TYPE_USERROOM = 11; // Internal type used by RoomShareProvider + + public const FORMAT_NONE = -1; + public const FORMAT_STATUSES = -2; + public const FORMAT_SOURCES = -3; // ToDo Check if it is still in use otherwise remove it + + public const RESPONSE_FORMAT = 'json'; // default resonse format for ocs calls + + public const TOKEN_LENGTH = 15; // old (oc7) length is 32, keep token length in db at least that for compatibility + + protected static $shareTypeUserAndGroups = -1; + protected static $shareTypeGroupUserUnique = 2; + protected static $backends = []; + protected static $backendTypes = []; + protected static $isResharingAllowed; +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share/Helper.php b/docker/overlays/nextcloud/html/lib/private/Share/Helper.php new file mode 100644 index 0000000..90dc3e9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share/Helper.php @@ -0,0 +1,294 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Miguel Prokop + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share; + +use OC\HintException; +use OCP\Share\IShare; + +class Helper extends \OC\Share\Constants { + + /** + * Generate a unique target for the item + * @param string $itemType + * @param string $itemSource + * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK + * @param string $shareWith User or group the item is being shared with + * @param string $uidOwner User that is the owner of shared item + * @param string $suggestedTarget The suggested target originating from a reshare (optional) + * @param int $groupParent The id of the parent group share (optional) + * @throws \Exception + * @return string Item target + */ + public static function generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $suggestedTarget = null, $groupParent = null) { + // FIXME: $uidOwner and $groupParent seems to be unused + $backend = \OC\Share\Share::getBackend($itemType); + if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_REMOTE) { + if (isset($suggestedTarget)) { + return $suggestedTarget; + } + return $backend->generateTarget($itemSource, false); + } else { + if ($shareType == IShare::TYPE_USER) { + // Share with is a user, so set share type to user and groups + $shareType = self::$shareTypeUserAndGroups; + } + + // Check if suggested target exists first + if (!isset($suggestedTarget)) { + $suggestedTarget = $itemSource; + } + if ($shareType == IShare::TYPE_GROUP) { + $target = $backend->generateTarget($suggestedTarget, false); + } else { + $target = $backend->generateTarget($suggestedTarget, $shareWith); + } + + return $target; + } + } + + /** + * Delete all reshares and group share children of an item + * @param int $parent Id of item to delete + * @param bool $excludeParent If true, exclude the parent from the delete (optional) + * @param string $uidOwner The user that the parent was shared with (optional) + * @param int $newParent new parent for the childrens + * @param bool $excludeGroupChildren exclude group children elements + */ + public static function delete($parent, $excludeParent = false, $uidOwner = null, $newParent = null, $excludeGroupChildren = false) { + $ids = [$parent]; + $deletedItems = []; + $changeParent = []; + $parents = [$parent]; + while (!empty($parents)) { + $parents = "'".implode("','", $parents)."'"; + // Check the owner on the first search of reshares, useful for + // finding and deleting the reshares by a single user of a group share + $params = []; + if (count($ids) == 1 && isset($uidOwner)) { + // FIXME: don't concat $parents, use Docrine's PARAM_INT_ARRAY approach + $queryString = 'SELECT `id`, `share_with`, `item_type`, `share_type`, ' . + '`item_target`, `file_target`, `parent` ' . + 'FROM `*PREFIX*share` ' . + 'WHERE `parent` IN ('.$parents.') AND `uid_owner` = ? '; + $params[] = $uidOwner; + } else { + $queryString = 'SELECT `id`, `share_with`, `item_type`, `share_type`, ' . + '`item_target`, `file_target`, `parent`, `uid_owner` ' . + 'FROM `*PREFIX*share` WHERE `parent` IN ('.$parents.') '; + } + if ($excludeGroupChildren) { + $queryString .= ' AND `share_type` != ?'; + $params[] = self::$shareTypeGroupUserUnique; + } + $query = \OC_DB::prepare($queryString); + $result = $query->execute($params); + // Reset parents array, only go through loop again if items are found + $parents = []; + while ($item = $result->fetchRow()) { + $tmpItem = [ + 'id' => $item['id'], + 'shareWith' => $item['share_with'], + 'itemTarget' => $item['item_target'], + 'itemType' => $item['item_type'], + 'shareType' => (int)$item['share_type'], + ]; + if (isset($item['file_target'])) { + $tmpItem['fileTarget'] = $item['file_target']; + } + // if we have a new parent for the child we remember the child + // to update the parent, if not we add it to the list of items + // which should be deleted + if ($newParent !== null) { + $changeParent[] = $item['id']; + } else { + $deletedItems[] = $tmpItem; + $ids[] = $item['id']; + $parents[] = $item['id']; + } + } + } + if ($excludeParent) { + unset($ids[0]); + } + + if (!empty($changeParent)) { + $idList = "'".implode("','", $changeParent)."'"; + $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `parent` = ? WHERE `id` IN ('.$idList.')'); + $query->execute([$newParent]); + } + + if (!empty($ids)) { + $idList = "'".implode("','", $ids)."'"; + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `id` IN ('.$idList.')'); + $query->execute(); + } + + return $deletedItems; + } + + /** + * get default expire settings defined by the admin + * @return array contains 'defaultExpireDateSet', 'enforceExpireDate', 'expireAfterDays' + */ + public static function getDefaultExpireSetting() { + $config = \OC::$server->getConfig(); + + $defaultExpireSettings = ['defaultExpireDateSet' => false]; + + // get default expire settings + $defaultExpireDate = $config->getAppValue('core', 'shareapi_default_expire_date', 'no'); + if ($defaultExpireDate === 'yes') { + $enforceExpireDate = $config->getAppValue('core', 'shareapi_enforce_expire_date', 'no'); + $defaultExpireSettings['defaultExpireDateSet'] = true; + $defaultExpireSettings['expireAfterDays'] = (int)$config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + $defaultExpireSettings['enforceExpireDate'] = $enforceExpireDate === 'yes'; + } + + return $defaultExpireSettings; + } + + public static function calcExpireDate() { + $expireAfter = \OC\Share\Share::getExpireInterval() * 24 * 60 * 60; + $expireAt = time() + $expireAfter; + $date = new \DateTime(); + $date->setTimestamp($expireAt); + $date->setTime(0, 0, 0); + //$dateString = $date->format('Y-m-d') . ' 00:00:00'; + + return $date; + } + + /** + * calculate expire date + * @param array $defaultExpireSettings contains 'defaultExpireDateSet', 'enforceExpireDate', 'expireAfterDays' + * @param int $creationTime timestamp when the share was created + * @param int $userExpireDate expire timestamp set by the user + * @return mixed integer timestamp or False + */ + public static function calculateExpireDate($defaultExpireSettings, $creationTime, $userExpireDate = null) { + $expires = false; + $defaultExpires = null; + + if (!empty($defaultExpireSettings['defaultExpireDateSet'])) { + $defaultExpires = $creationTime + $defaultExpireSettings['expireAfterDays'] * 86400; + } + + + if (isset($userExpireDate)) { + // if the admin decided to enforce the default expire date then we only take + // the user defined expire date of it is before the default expire date + if ($defaultExpires && !empty($defaultExpireSettings['enforceExpireDate'])) { + $expires = min($userExpireDate, $defaultExpires); + } else { + $expires = $userExpireDate; + } + } elseif ($defaultExpires && !empty($defaultExpireSettings['enforceExpireDate'])) { + $expires = $defaultExpires; + } + + return $expires; + } + + /** + * Strips away a potential file names and trailing slashes: + * - http://localhost + * - http://localhost/ + * - http://localhost/index.php + * - http://localhost/index.php/s/{shareToken} + * + * all return: http://localhost + * + * @param string $remote + * @return string + */ + protected static function fixRemoteURL($remote) { + $remote = str_replace('\\', '/', $remote); + if ($fileNamePosition = strpos($remote, '/index.php')) { + $remote = substr($remote, 0, $fileNamePosition); + } + $remote = rtrim($remote, '/'); + + return $remote; + } + + /** + * split user and remote from federated cloud id + * + * @param string $id + * @return string[] + * @throws HintException + */ + public static function splitUserRemote($id) { + try { + $cloudId = \OC::$server->getCloudIdManager()->resolveCloudId($id); + return [$cloudId->getUser(), $cloudId->getRemote()]; + } catch (\InvalidArgumentException $e) { + $l = \OC::$server->getL10N('core'); + $hint = $l->t('Invalid Federated Cloud ID'); + throw new HintException('Invalid Federated Cloud ID', $hint, 0, $e); + } + } + + /** + * check if two federated cloud IDs refer to the same user + * + * @param string $user1 + * @param string $server1 + * @param string $user2 + * @param string $server2 + * @return bool true if both users and servers are the same + */ + public static function isSameUserOnSameServer($user1, $server1, $user2, $server2) { + $normalizedServer1 = strtolower(\OC\Share\Share::removeProtocolFromUrl($server1)); + $normalizedServer2 = strtolower(\OC\Share\Share::removeProtocolFromUrl($server2)); + + if (rtrim($normalizedServer1, '/') === rtrim($normalizedServer2, '/')) { + // FIXME this should be a method in the user management instead + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + ['uid' => &$user1] + ); + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + ['uid' => &$user2] + ); + + if ($user1 === $user2) { + return true; + } + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share/SearchResultSorter.php b/docker/overlays/nextcloud/html/lib/private/Share/SearchResultSorter.php new file mode 100644 index 0000000..2151eb2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share/SearchResultSorter.php @@ -0,0 +1,78 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share; + +use OCP\ILogger; + +class SearchResultSorter { + private $search; + private $encoding; + private $key; + private $log; + + /** + * @param string $search the search term as was given by the user + * @param string $key the array key containing the value that should be compared + * against + * @param string $encoding optional, encoding to use, defaults to UTF-8 + * @param ILogger $log optional + */ + public function __construct($search, $key, ILogger $log = null, $encoding = 'UTF-8') { + $this->encoding = $encoding; + $this->key = $key; + $this->log = $log; + $this->search = mb_strtolower($search, $this->encoding); + } + + /** + * User and Group names matching the search term at the beginning shall appear + * on top of the share dialog. Following entries in alphabetical order. + * Callback function for usort. http://php.net/usort + */ + public function sort($a, $b) { + if (!isset($a[$this->key]) || !isset($b[$this->key])) { + if (!is_null($this->log)) { + $this->log->error('Sharing dialogue: cannot sort due to ' . + 'missing array key', ['app' => 'core']); + } + return 0; + } + $nameA = mb_strtolower($a[$this->key], $this->encoding); + $nameB = mb_strtolower($b[$this->key], $this->encoding); + $i = mb_strpos($nameA, $this->search, 0, $this->encoding); + $j = mb_strpos($nameB, $this->search, 0, $this->encoding); + + if ($i === $j || $i > 0 && $j > 0) { + return strcmp(mb_strtolower($nameA, $this->encoding), + mb_strtolower($nameB, $this->encoding)); + } elseif ($i === 0) { + return -1; + } else { + return 1; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share/Share.php b/docker/overlays/nextcloud/html/lib/private/Share/Share.php new file mode 100644 index 0000000..aed2336 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share/Share.php @@ -0,0 +1,1143 @@ + + * @author Bart Visscher + * @author Bernhard Reiter + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christoph Wurst + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sebastian Döll + * @author Thomas Müller + * @author Vincent Petry + * @author Volkan Gezer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\ILogger; +use OCP\Share\IShare; + +/** + * This class provides the ability for apps to share their content between users. + * Apps must create a backend class that implements OCP\Share_Backend and register it with this class. + * + * It provides the following hooks: + * - post_shared + */ +class Share extends Constants { + + /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask + * Construct permissions for share() and setPermissions with Or (|) e.g. + * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE + * + * Check if permission is granted with And (&) e.g. Check if delete is + * granted: if ($permissions & PERMISSION_DELETE) + * + * Remove permissions with And (&) and Not (~) e.g. Remove the update + * permission: $permissions &= ~PERMISSION_UPDATE + * + * Apps are required to handle permissions on their own, this class only + * stores and manages the permissions of shares + * @see lib/public/constants.php + */ + + /** + * Register a sharing backend class that implements OCP\Share_Backend for an item type + * @param string $itemType Item type + * @param string $class Backend class + * @param string $collectionOf (optional) Depends on item type + * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files + * @return boolean true if backend is registered or false if error + */ + public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) { + if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') { + if (!isset(self::$backendTypes[$itemType])) { + self::$backendTypes[$itemType] = [ + 'class' => $class, + 'collectionOf' => $collectionOf, + 'supportedFileExtensions' => $supportedFileExtensions + ]; + return true; + } + \OCP\Util::writeLog('OCP\Share', + 'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class'] + .' is already registered for '.$itemType, + ILogger::WARN); + } + return false; + } + + /** + * Get the items of item type shared with the current user + * @param string $itemType + * @param int $format (optional) Format type must be defined by the backend + * @param mixed $parameters (optional) + * @param int $limit Number of items to return (optional) Returns all by default + * @param boolean $includeCollections (optional) + * @return mixed Return depends on format + * @deprecated TESTS ONLY - this methods is only used by tests + * called like this: + * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php) + */ + public static function getItemsSharedWith() { + return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE, + null, -1, false); + } + + /** + * Get the items of item type shared with a user + * @param string $itemType + * @param string $user id for which user we want the shares + * @param int $format (optional) Format type must be defined by the backend + * @param mixed $parameters (optional) + * @param int $limit Number of items to return (optional) Returns all by default + * @param boolean $includeCollections (optional) + * @return mixed Return depends on format + */ + public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE, + $parameters = null, $limit = -1, $includeCollections = false) { + return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format, + $parameters, $limit, $includeCollections); + } + + /** + * Get the item of item type shared with a given user by source + * @param string $itemType + * @param string $itemSource + * @param string $user User to whom the item was shared + * @param string $owner Owner of the share + * @param int $shareType only look for a specific share type + * @return array Return list of items with file_target, permissions and expiration + */ + public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) { + $shares = []; + $fileDependent = false; + + $where = 'WHERE'; + $fileDependentWhere = ''; + if ($itemType === 'file' || $itemType === 'folder') { + $fileDependent = true; + $column = 'file_source'; + $fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` '; + $fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` '; + } else { + $column = 'item_source'; + } + + $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent); + + $where .= ' `' . $column . '` = ? AND `item_type` = ? '; + $arguments = [$itemSource, $itemType]; + // for link shares $user === null + if ($user !== null) { + $where .= ' AND `share_with` = ? '; + $arguments[] = $user; + } + + if ($shareType !== null) { + $where .= ' AND `share_type` = ? '; + $arguments[] = $shareType; + } + + if ($owner !== null) { + $where .= ' AND `uid_owner` = ? '; + $arguments[] = $owner; + } + + $query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where); + + $result = \OC_DB::executeAudited($query, $arguments); + + while ($row = $result->fetchRow()) { + if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) { + continue; + } + if ($fileDependent && (int)$row['file_parent'] === -1) { + // if it is a mount point we need to get the path from the mount manager + $mountManager = \OC\Files\Filesystem::getMountManager(); + $mountPoint = $mountManager->findByStorageId($row['storage_id']); + if (!empty($mountPoint)) { + $path = $mountPoint[0]->getMountPoint(); + $path = trim($path, '/'); + $path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt` + $row['path'] = $path; + } else { + \OC::$server->getLogger()->warning( + 'Could not resolve mount point for ' . $row['storage_id'], + ['app' => 'OCP\Share'] + ); + } + } + $shares[] = $row; + } + + //if didn't found a result than let's look for a group share. + if (empty($shares) && $user !== null) { + $userObject = \OC::$server->getUserManager()->get($user); + $groups = []; + if ($userObject) { + $groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject); + } + + if (!empty($groups)) { + $where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)'; + $arguments = [$itemSource, $itemType, $groups]; + $types = [null, null, IQueryBuilder::PARAM_STR_ARRAY]; + + if ($owner !== null) { + $where .= ' AND `uid_owner` = ?'; + $arguments[] = $owner; + $types[] = null; + } + + // TODO: inject connection, hopefully one day in the future when this + // class isn't static anymore... + $conn = \OC::$server->getDatabaseConnection(); + $result = $conn->executeQuery( + 'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where, + $arguments, + $types + ); + + while ($row = $result->fetch()) { + $shares[] = $row; + } + } + } + + return $shares; + } + + /** + * Get the item of item type shared with the current user by source + * @param string $itemType + * @param string $itemSource + * @param int $format (optional) Format type must be defined by the backend + * @param mixed $parameters + * @param boolean $includeCollections + * @param string $shareWith (optional) define against which user should be checked, default: current user + * @return array + */ + public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE, + $parameters = null, $includeCollections = false, $shareWith = null) { + $shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith; + return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format, + $parameters, 1, $includeCollections, true); + } + + /** + * Get the shared item of item type owned by the current user + * @param string $itemType + * @param string $itemSource + * @param int $format (optional) Format type must be defined by the backend + * @param mixed $parameters + * @param boolean $includeCollections + * @return mixed Return depends on format + */ + public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE, + $parameters = null, $includeCollections = false) { + return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format, + $parameters, -1, $includeCollections); + } + + /** + * Unshare an item from a user, group, or delete a private link + * @param string $itemType + * @param string $itemSource + * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK + * @param string $shareWith User or group the item is being shared with + * @param string $owner owner of the share, if null the current user is used + * @return boolean true on success or false on failure + */ + public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) { + + // check if it is a valid itemType + self::getBackend($itemType); + + $items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType); + + $toDelete = []; + $newParent = null; + $currentUser = $owner ? $owner : \OC_User::getUser(); + foreach ($items as $item) { + // delete the item with the expected share_type and owner + if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) { + $toDelete = $item; + // if there is more then one result we don't have to delete the children + // but update their parent. For group shares the new parent should always be + // the original group share and not the db entry with the unique name + } elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) { + $newParent = $item['parent']; + } else { + $newParent = $item['id']; + } + } + + if (!empty($toDelete)) { + self::unshareItem($toDelete, $newParent); + return true; + } + return false; + } + + /** + * Checks whether a share has expired, calls unshareItem() if yes. + * @param array $item Share data (usually database row) + * @return boolean True if item was expired, false otherwise. + */ + protected static function expireItem(array $item) { + $result = false; + + // only use default expiration date for link shares + if ((int) $item['share_type'] === IShare::TYPE_LINK) { + + // calculate expiration date + if (!empty($item['expiration'])) { + $userDefinedExpire = new \DateTime($item['expiration']); + $expires = $userDefinedExpire->getTimestamp(); + } else { + $expires = null; + } + + + // get default expiration settings + $defaultSettings = Helper::getDefaultExpireSetting(); + $expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires); + + + if (is_int($expires)) { + $now = time(); + if ($now > $expires) { + self::unshareItem($item); + $result = true; + } + } + } + return $result; + } + + /** + * Unshares a share given a share data array + * @param array $item Share data (usually database row) + * @param int $newParent parent ID + * @return null + */ + protected static function unshareItem(array $item, $newParent = null) { + $shareType = (int)$item['share_type']; + $shareWith = null; + if ($shareType !== IShare::TYPE_LINK) { + $shareWith = $item['share_with']; + } + + // Pass all the vars we have for now, they may be useful + $hookParams = [ + 'id' => $item['id'], + 'itemType' => $item['item_type'], + 'itemSource' => $item['item_source'], + 'shareType' => $shareType, + 'shareWith' => $shareWith, + 'itemParent' => $item['parent'], + 'uidOwner' => $item['uid_owner'], + ]; + if ($item['item_type'] === 'file' || $item['item_type'] === 'folder') { + $hookParams['fileSource'] = $item['file_source']; + $hookParams['fileTarget'] = $item['file_target']; + } + + \OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams); + $deletedShares = Helper::delete($item['id'], false, null, $newParent); + $deletedShares[] = $hookParams; + $hookParams['deletedShares'] = $deletedShares; + \OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams); + if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) { + list(, $remote) = Helper::splitUserRemote($item['share_with']); + self::sendRemoteUnshare($remote, $item['id'], $item['token']); + } + } + + /** + * Get the backend class for the specified item type + * @param string $itemType + * @throws \Exception + * @return \OCP\Share_Backend + */ + public static function getBackend($itemType) { + $l = \OC::$server->getL10N('lib'); + if (isset(self::$backends[$itemType])) { + return self::$backends[$itemType]; + } elseif (isset(self::$backendTypes[$itemType]['class'])) { + $class = self::$backendTypes[$itemType]['class']; + if (class_exists($class)) { + self::$backends[$itemType] = new $class; + if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) { + $message = 'Sharing backend %s must implement the interface OCP\Share_Backend'; + $message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]); + \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR); + throw new \Exception($message_t); + } + return self::$backends[$itemType]; + } else { + $message = 'Sharing backend %s not found'; + $message_t = $l->t('Sharing backend %s not found', [$class]); + \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR); + throw new \Exception($message_t); + } + } + $message = 'Sharing backend for %s not found'; + $message_t = $l->t('Sharing backend for %s not found', [$itemType]); + \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR); + throw new \Exception($message_t); + } + + /** + * Check if resharing is allowed + * @return boolean true if allowed or false + * + * Resharing is allowed by default if not configured + */ + public static function isResharingAllowed() { + if (!isset(self::$isResharingAllowed)) { + if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') { + self::$isResharingAllowed = true; + } else { + self::$isResharingAllowed = false; + } + } + return self::$isResharingAllowed; + } + + /** + * Get a list of collection item types for the specified item type + * @param string $itemType + * @return array + */ + private static function getCollectionItemTypes($itemType) { + $collectionTypes = [$itemType]; + foreach (self::$backendTypes as $type => $backend) { + if (in_array($backend['collectionOf'], $collectionTypes)) { + $collectionTypes[] = $type; + } + } + // TODO Add option for collections to be collection of themselves, only 'folder' does it now... + if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) { + unset($collectionTypes[0]); + } + // Return array if collections were found or the item type is a + // collection itself - collections can be inside collections + if (count($collectionTypes) > 0) { + return $collectionTypes; + } + return false; + } + + /** + * Get shared items from the database + * @param string $itemType + * @param string $item Item source or target (optional) + * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique + * @param string $shareWith User or group the item is being shared with + * @param string $uidOwner User that is the owner of shared items (optional) + * @param int $format Format to convert items to with formatItems() (optional) + * @param mixed $parameters to pass to formatItems() (optional) + * @param int $limit Number of items to return, -1 to return all matches (optional) + * @param boolean $includeCollections Include collection item types (optional) + * @param boolean $itemShareWithBySource (optional) + * @param boolean $checkExpireDate + * @return array + * + * See public functions getItem(s)... for parameter usage + * + */ + public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null, + $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, + $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { + if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') { + return []; + } + $backend = self::getBackend($itemType); + $collectionTypes = false; + // Get filesystem root to add it to the file target and remove from the + // file source, match file_source with the file cache + if ($itemType == 'file' || $itemType == 'folder') { + if (!is_null($uidOwner)) { + $root = \OC\Files\Filesystem::getRoot(); + } else { + $root = ''; + } + $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` '; + if (!isset($item)) { + $where .= ' AND `file_target` IS NOT NULL '; + } + $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` '; + $fileDependent = true; + $queryArgs = []; + } else { + $fileDependent = false; + $root = ''; + $collectionTypes = self::getCollectionItemTypes($itemType); + if ($includeCollections && !isset($item) && $collectionTypes) { + // If includeCollections is true, find collections of this item type, e.g. a music album contains songs + if (!in_array($itemType, $collectionTypes)) { + $itemTypes = array_merge([$itemType], $collectionTypes); + } else { + $itemTypes = $collectionTypes; + } + $placeholders = implode(',', array_fill(0, count($itemTypes), '?')); + $where = ' WHERE `item_type` IN ('.$placeholders.'))'; + $queryArgs = $itemTypes; + } else { + $where = ' WHERE `item_type` = ?'; + $queryArgs = [$itemType]; + } + } + if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') { + $where .= ' AND `share_type` != ?'; + $queryArgs[] = IShare::TYPE_LINK; + } + if (isset($shareType)) { + // Include all user and group items + if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) { + $where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) '; + $queryArgs[] = IShare::TYPE_USER; + $queryArgs[] = self::$shareTypeGroupUserUnique; + $queryArgs[] = $shareWith; + + $user = \OC::$server->getUserManager()->get($shareWith); + $groups = []; + if ($user) { + $groups = \OC::$server->getGroupManager()->getUserGroupIds($user); + } + if (!empty($groups)) { + $placeholders = implode(',', array_fill(0, count($groups), '?')); + $where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) '; + $queryArgs[] = IShare::TYPE_GROUP; + $queryArgs = array_merge($queryArgs, $groups); + } + $where .= ')'; + // Don't include own group shares + $where .= ' AND `uid_owner` != ?'; + $queryArgs[] = $shareWith; + } else { + $where .= ' AND `share_type` = ?'; + $queryArgs[] = $shareType; + if (isset($shareWith)) { + $where .= ' AND `share_with` = ?'; + $queryArgs[] = $shareWith; + } + } + } + if (isset($uidOwner)) { + $where .= ' AND `uid_owner` = ?'; + $queryArgs[] = $uidOwner; + if (!isset($shareType)) { + // Prevent unique user targets for group shares from being selected + $where .= ' AND `share_type` != ?'; + $queryArgs[] = self::$shareTypeGroupUserUnique; + } + if ($fileDependent) { + $column = 'file_source'; + } else { + $column = 'item_source'; + } + } else { + if ($fileDependent) { + $column = 'file_target'; + } else { + $column = 'item_target'; + } + } + if (isset($item)) { + $collectionTypes = self::getCollectionItemTypes($itemType); + if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) { + $where .= ' AND ('; + } else { + $where .= ' AND'; + } + // If looking for own shared items, check item_source else check item_target + if (isset($uidOwner) || $itemShareWithBySource) { + // If item type is a file, file source needs to be checked in case the item was converted + if ($fileDependent) { + $where .= ' `file_source` = ?'; + $column = 'file_source'; + } else { + $where .= ' `item_source` = ?'; + $column = 'item_source'; + } + } else { + if ($fileDependent) { + $where .= ' `file_target` = ?'; + $item = \OC\Files\Filesystem::normalizePath($item); + } else { + $where .= ' `item_target` = ?'; + } + } + $queryArgs[] = $item; + if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) { + $placeholders = implode(',', array_fill(0, count($collectionTypes), '?')); + $where .= ' OR `item_type` IN ('.$placeholders.'))'; + $queryArgs = array_merge($queryArgs, $collectionTypes); + } + } + + if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) { + // Make sure the unique user target is returned if it exists, + // unique targets should follow the group share in the database + // If the limit is not 1, the filtering can be done later + $where .= ' ORDER BY `*PREFIX*share`.`id` DESC'; + } else { + $where .= ' ORDER BY `*PREFIX*share`.`id` ASC'; + } + + if ($limit != -1 && !$includeCollections) { + // The limit must be at least 3, because filtering needs to be done + if ($limit < 3) { + $queryLimit = 3; + } else { + $queryLimit = $limit; + } + } else { + $queryLimit = null; + } + $select = self::createSelectStatement($format, $fileDependent, $uidOwner); + $root = strlen($root); + $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit); + $result = $query->execute($queryArgs); + if ($result === false) { + \OCP\Util::writeLog('OCP\Share', + \OC_DB::getErrorMessage() . ', select=' . $select . ' where=', + ILogger::ERROR); + } + $items = []; + $targets = []; + $switchedItems = []; + $mounts = []; + while ($row = $result->fetchRow()) { + self::transformDBResults($row); + // Filter out duplicate group shares for users with unique targets + if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) { + continue; + } + if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) { + $row['share_type'] = IShare::TYPE_GROUP; + $row['unique_name'] = true; // remember that we use a unique name for this user + $row['share_with'] = $items[$row['parent']]['share_with']; + // if the group share was unshared from the user we keep the permission, otherwise + // we take the permission from the parent because this is always the up-to-date + // permission for the group share + if ($row['permissions'] > 0) { + $row['permissions'] = $items[$row['parent']]['permissions']; + } + // Remove the parent group share + unset($items[$row['parent']]); + if ($row['permissions'] == 0) { + continue; + } + } elseif (!isset($uidOwner)) { + // Check if the same target already exists + if (isset($targets[$row['id']])) { + // Check if the same owner shared with the user twice + // through a group and user share - this is allowed + $id = $targets[$row['id']]; + if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) { + // Switch to group share type to ensure resharing conditions aren't bypassed + if ($items[$id]['share_type'] != IShare::TYPE_GROUP) { + $items[$id]['share_type'] = IShare::TYPE_GROUP; + $items[$id]['share_with'] = $row['share_with']; + } + // Switch ids if sharing permission is granted on only + // one share to ensure correct parent is used if resharing + if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE + && (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) { + $items[$row['id']] = $items[$id]; + $switchedItems[$id] = $row['id']; + unset($items[$id]); + $id = $row['id']; + } + $items[$id]['permissions'] |= (int)$row['permissions']; + } + continue; + } elseif (!empty($row['parent'])) { + $targets[$row['parent']] = $row['id']; + } + } + // Remove root from file source paths if retrieving own shared items + if (isset($uidOwner) && isset($row['path'])) { + if (isset($row['parent'])) { + $query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?'); + $parentResult = $query->execute([$row['parent']]); + if ($result === false) { + \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' . + \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where, + ILogger::ERROR); + } else { + $parentRow = $parentResult->fetchRow(); + $tmpPath = $parentRow['file_target']; + // find the right position where the row path continues from the target path + $pos = strrpos($row['path'], $parentRow['file_target']); + $subPath = substr($row['path'], $pos); + $splitPath = explode('/', $subPath); + foreach (array_slice($splitPath, 2) as $pathPart) { + $tmpPath = $tmpPath . '/' . $pathPart; + } + $row['path'] = $tmpPath; + } + } else { + if (!isset($mounts[$row['storage']])) { + $mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']); + if (is_array($mountPoints) && !empty($mountPoints)) { + $mounts[$row['storage']] = current($mountPoints); + } + } + if (!empty($mounts[$row['storage']])) { + $path = $mounts[$row['storage']]->getMountPoint().$row['path']; + $relPath = substr($path, $root); // path relative to data/user + $row['path'] = rtrim($relPath, '/'); + } + } + } + + if ($checkExpireDate) { + if (self::expireItem($row)) { + continue; + } + } + // Check if resharing is allowed, if not remove share permission + if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) { + $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE; + } + // Add display names to result + $row['share_with_displayname'] = $row['share_with']; + if (isset($row['share_with']) && $row['share_with'] != '' && + $row['share_type'] === IShare::TYPE_USER) { + $shareWithUser = \OC::$server->getUserManager()->get($row['share_with']); + $row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName(); + } elseif (isset($row['share_with']) && $row['share_with'] != '' && + $row['share_type'] === IShare::TYPE_REMOTE) { + $addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']); + foreach ($addressBookEntries as $entry) { + foreach ($entry['CLOUD'] as $cloudID) { + if ($cloudID === $row['share_with']) { + $row['share_with_displayname'] = $entry['FN']; + } + } + } + } + if (isset($row['uid_owner']) && $row['uid_owner'] != '') { + $ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']); + $row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName(); + } + + if ($row['permissions'] > 0) { + $items[$row['id']] = $row; + } + } + + // group items if we are looking for items shared with the current user + if (isset($shareWith) && $shareWith === \OCP\User::getUser()) { + $items = self::groupItems($items, $itemType); + } + + if (!empty($items)) { + $collectionItems = []; + foreach ($items as &$row) { + // Return only the item instead of a 2-dimensional array + if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) { + if ($format == self::FORMAT_NONE) { + return $row; + } else { + break; + } + } + // Check if this is a collection of the requested item type + if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) { + if (($collectionBackend = self::getBackend($row['item_type'])) + && $collectionBackend instanceof \OCP\Share_Backend_Collection) { + // Collections can be inside collections, check if the item is a collection + if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) { + $collectionItems[] = $row; + } else { + $collection = []; + $collection['item_type'] = $row['item_type']; + if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') { + $collection['path'] = basename($row['path']); + } + $row['collection'] = $collection; + // Fetch all of the children sources + $children = $collectionBackend->getChildren($row[$column]); + foreach ($children as $child) { + $childItem = $row; + $childItem['item_type'] = $itemType; + if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') { + $childItem['item_source'] = $child['source']; + $childItem['item_target'] = $child['target']; + } + if ($backend instanceof \OCP\Share_Backend_File_Dependent) { + if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') { + $childItem['file_source'] = $child['source']; + } else { // TODO is this really needed if we already know that we use the file backend? + $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']); + $childItem['file_source'] = $meta['fileid']; + } + $childItem['file_target'] = + \OC\Files\Filesystem::normalizePath($child['file_path']); + } + if (isset($item)) { + if ($childItem[$column] == $item) { + // Return only the item instead of a 2-dimensional array + if ($limit == 1) { + if ($format == self::FORMAT_NONE) { + return $childItem; + } else { + // Unset the items array and break out of both loops + $items = []; + $items[] = $childItem; + break 2; + } + } else { + $collectionItems[] = $childItem; + } + } + } else { + $collectionItems[] = $childItem; + } + } + } + } + // Remove collection item + $toRemove = $row['id']; + if (array_key_exists($toRemove, $switchedItems)) { + $toRemove = $switchedItems[$toRemove]; + } + unset($items[$toRemove]); + } elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) { + // FIXME: Thats a dirty hack to improve file sharing performance, + // see github issue #10588 for more details + // Need to find a solution which works for all back-ends + $collectionBackend = self::getBackend($row['item_type']); + $sharedParents = $collectionBackend->getParents($row['item_source']); + foreach ($sharedParents as $parent) { + $collectionItems[] = $parent; + } + } + } + if (!empty($collectionItems)) { + $collectionItems = array_unique($collectionItems, SORT_REGULAR); + $items = array_merge($items, $collectionItems); + } + + // filter out invalid items, these can appear when subshare entries exist + // for a group in which the requested user isn't a member any more + $items = array_filter($items, function ($item) { + return $item['share_type'] !== self::$shareTypeGroupUserUnique; + }); + + return self::formatResult($items, $column, $backend, $format, $parameters); + } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) { + // FIXME: Thats a dirty hack to improve file sharing performance, + // see github issue #10588 for more details + // Need to find a solution which works for all back-ends + $collectionItems = []; + $collectionBackend = self::getBackend('folder'); + $sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner); + foreach ($sharedParents as $parent) { + $collectionItems[] = $parent; + } + if ($limit === 1) { + return reset($collectionItems); + } + return self::formatResult($collectionItems, $column, $backend, $format, $parameters); + } + + return []; + } + + /** + * group items with link to the same source + * + * @param array $items + * @param string $itemType + * @return array of grouped items + */ + protected static function groupItems($items, $itemType) { + $fileSharing = $itemType === 'file' || $itemType === 'folder'; + + $result = []; + + foreach ($items as $item) { + $grouped = false; + foreach ($result as $key => $r) { + // for file/folder shares we need to compare file_source, otherwise we compare item_source + // only group shares if they already point to the same target, otherwise the file where shared + // before grouping of shares was added. In this case we don't group them toi avoid confusions + if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) || + (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { + // add the first item to the list of grouped shares + if (!isset($result[$key]['grouped'])) { + $result[$key]['grouped'][] = $result[$key]; + } + $result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions']; + $result[$key]['grouped'][] = $item; + $grouped = true; + break; + } + } + + if (!$grouped) { + $result[] = $item; + } + } + + return $result; + } + + /** + * construct select statement + * @param int $format + * @param boolean $fileDependent ist it a file/folder share or a generla share + * @param string $uidOwner + * @return string select statement + */ + private static function createSelectStatement($format, $fileDependent, $uidOwner = null) { + $select = '*'; + if ($format == self::FORMAT_STATUSES) { + if ($fileDependent) { + $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, ' + . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, ' + . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, ' + . '`uid_initiator`'; + } else { + $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`'; + } + } else { + if (isset($uidOwner)) { + if ($fileDependent) { + $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,' + . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,' + . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, ' + . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`'; + } else { + $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,' + . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`'; + } + } else { + if ($fileDependent) { + if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) { + $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, ' + . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, ' + . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, ' + . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`'; + } else { + $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,' + . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,' + . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,' + . '`stime`, `expiration`, `token`, `storage`, `mail_send`,' + . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`'; + } + } + } + } + return $select; + } + + + /** + * transform db results + * @param array $row result + */ + private static function transformDBResults(&$row) { + if (isset($row['id'])) { + $row['id'] = (int) $row['id']; + } + if (isset($row['share_type'])) { + $row['share_type'] = (int) $row['share_type']; + } + if (isset($row['parent'])) { + $row['parent'] = (int) $row['parent']; + } + if (isset($row['file_parent'])) { + $row['file_parent'] = (int) $row['file_parent']; + } + if (isset($row['file_source'])) { + $row['file_source'] = (int) $row['file_source']; + } + if (isset($row['permissions'])) { + $row['permissions'] = (int) $row['permissions']; + } + if (isset($row['storage'])) { + $row['storage'] = (int) $row['storage']; + } + if (isset($row['stime'])) { + $row['stime'] = (int) $row['stime']; + } + if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) { + // discard expiration date for non-link shares, which might have been + // set by ancient bugs + $row['expiration'] = null; + } + } + + /** + * format result + * @param array $items result + * @param string $column is it a file share or a general share ('file_target' or 'item_target') + * @param \OCP\Share_Backend $backend sharing backend + * @param int $format + * @param array $parameters additional format parameters + * @return array format result + */ + private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) { + if ($format === self::FORMAT_NONE) { + return $items; + } elseif ($format === self::FORMAT_STATUSES) { + $statuses = []; + foreach ($items as $item) { + if ($item['share_type'] === IShare::TYPE_LINK) { + if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) { + continue; + } + $statuses[$item[$column]]['link'] = true; + } elseif (!isset($statuses[$item[$column]])) { + $statuses[$item[$column]]['link'] = false; + } + if (!empty($item['file_target'])) { + $statuses[$item[$column]]['path'] = $item['path']; + } + } + return $statuses; + } else { + return $backend->formatItems($items, $format, $parameters); + } + } + + /** + * remove protocol from URL + * + * @param string $url + * @return string + */ + public static function removeProtocolFromUrl($url) { + if (strpos($url, 'https://') === 0) { + return substr($url, strlen('https://')); + } elseif (strpos($url, 'http://') === 0) { + return substr($url, strlen('http://')); + } + + return $url; + } + + /** + * try http post first with https and then with http as a fallback + * + * @param string $remoteDomain + * @param string $urlSuffix + * @param array $fields post parameters + * @return array + */ + private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) { + $protocol = 'https://'; + $result = [ + 'success' => false, + 'result' => '', + ]; + $try = 0; + $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class); + while ($result['success'] === false && $try < 2) { + $federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING'); + $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares'; + $client = \OC::$server->getHTTPClientService()->newClient(); + + try { + $response = $client->post( + $protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, + [ + 'body' => $fields, + 'connect_timeout' => 10, + ] + ); + + $result = ['success' => true, 'result' => $response->getBody()]; + } catch (\Exception $e) { + $result = ['success' => false, 'result' => $e->getMessage()]; + } + + $try++; + $protocol = 'http://'; + } + + return $result; + } + + /** + * send server-to-server unshare to remote server + * + * @param string $remote url + * @param int $id share id + * @param string $token + * @return bool + */ + private static function sendRemoteUnshare($remote, $id, $token) { + $url = rtrim($remote, '/'); + $fields = ['token' => $token, 'format' => 'json']; + $url = self::removeProtocolFromUrl($url); + $result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields); + $status = json_decode($result['result'], true); + + return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)); + } + + /** + * @return int + */ + public static function getExpireInterval() { + return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + } + + /** + * Checks whether the given path is reachable for the given owner + * + * @param string $path path relative to files + * @param string $ownerStorageId storage id of the owner + * + * @return boolean true if file is reachable, false otherwise + */ + private static function isFileReachable($path, $ownerStorageId) { + // if outside the home storage, file is always considered reachable + if (!(substr($ownerStorageId, 0, 6) === 'home::' || + substr($ownerStorageId, 0, 13) === 'object::user:' + )) { + return true; + } + + // if inside the home storage, the file has to be under "/files/" + $path = ltrim($path, '/'); + if (substr($path, 0, 6) === 'files/') { + return true; + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/DefaultShareProvider.php b/docker/overlays/nextcloud/html/lib/private/Share20/DefaultShareProvider.php new file mode 100644 index 0000000..e435290 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/DefaultShareProvider.php @@ -0,0 +1,1497 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Jan-Philipp Litza + * @author Joas Schilling + * @author Julius Härtl + * @author Lukas Reschke + * @author Maxence Lange + * @author phisch + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20; + +use OC\Files\Cache\Cache; +use OC\Share20\Exception\BackendError; +use OC\Share20\Exception\InvalidShare; +use OC\Share20\Exception\ProviderException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Defaults; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\L10N\IFactory; +use OCP\Mail\IMailer; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IShare; +use OCP\Share\IShareProvider; + +/** + * Class DefaultShareProvider + * + * @package OC\Share20 + */ +class DefaultShareProvider implements IShareProvider { + + // Special share type for user modified group shares + public const SHARE_TYPE_USERGROUP = 2; + + /** @var IDBConnection */ + private $dbConn; + + /** @var IUserManager */ + private $userManager; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var IMailer */ + private $mailer; + + /** @var Defaults */ + private $defaults; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var IConfig */ + private $config; + + public function __construct( + IDBConnection $connection, + IUserManager $userManager, + IGroupManager $groupManager, + IRootFolder $rootFolder, + IMailer $mailer, + Defaults $defaults, + IFactory $l10nFactory, + IURLGenerator $urlGenerator, + IConfig $config) { + $this->dbConn = $connection; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->rootFolder = $rootFolder; + $this->mailer = $mailer; + $this->defaults = $defaults; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + $this->config = $config; + } + + /** + * Return the identifier of this provider. + * + * @return string Containing only [a-zA-Z0-9] + */ + public function identifier() { + return 'ocinternal'; + } + + /** + * Share a path + * + * @param \OCP\Share\IShare $share + * @return \OCP\Share\IShare The share object + * @throws ShareNotFound + * @throws \Exception + */ + public function create(\OCP\Share\IShare $share) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->insert('share'); + $qb->setValue('share_type', $qb->createNamedParameter($share->getShareType())); + + if ($share->getShareType() === IShare::TYPE_USER) { + //Set the UID of the user we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith())); + $qb->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING)); + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + //Set the GID of the group we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith())); + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + //set label for public link + $qb->setValue('label', $qb->createNamedParameter($share->getLabel())); + //Set the token of the share + $qb->setValue('token', $qb->createNamedParameter($share->getToken())); + + //If a password is set store it + if ($share->getPassword() !== null) { + $qb->setValue('password', $qb->createNamedParameter($share->getPassword())); + } + + $qb->setValue('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL)); + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } + + if (method_exists($share, 'getParent')) { + $qb->setValue('parent', $qb->createNamedParameter($share->getParent())); + } + } else { + throw new \Exception('invalid share type!'); + } + + // Set what is shares + $qb->setValue('item_type', $qb->createParameter('itemType')); + if ($share->getNode() instanceof \OCP\Files\File) { + $qb->setParameter('itemType', 'file'); + } else { + $qb->setParameter('itemType', 'folder'); + } + + // Set the file id + $qb->setValue('item_source', $qb->createNamedParameter($share->getNode()->getId())); + $qb->setValue('file_source', $qb->createNamedParameter($share->getNode()->getId())); + + // set the permissions + $qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions())); + + // Set who created this share + $qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy())); + + // Set who is the owner of this file/folder (and this the owner of the share) + $qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner())); + + // Set the file target + $qb->setValue('file_target', $qb->createNamedParameter($share->getTarget())); + + // Set the time this share was created + $qb->setValue('stime', $qb->createNamedParameter(time())); + + // insert the data and fetch the id of the share + $this->dbConn->beginTransaction(); + $qb->execute(); + $id = $this->dbConn->lastInsertId('*PREFIX*share'); + + // Now fetch the inserted share and create a complete share object + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $this->dbConn->commit(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ShareNotFound(); + } + + $mailSendValue = $share->getMailSend(); + $data['mail_send'] = ($mailSendValue === null) ? true : $mailSendValue; + + $share = $this->createShare($data); + return $share; + } + + /** + * Update a share + * + * @param \OCP\Share\IShare $share + * @return \OCP\Share\IShare The share object + * @throws ShareNotFound + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException + */ + public function update(\OCP\Share\IShare $share) { + $originalShare = $this->getShareById($share->getId()); + + if ($share->getShareType() === IShare::TYPE_USER) { + /* + * We allow updating the recipient on user shares. + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) + ->set('share_with', $qb->createNamedParameter($share->getSharedWith())) + ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) + ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) + ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) + ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('accepted', $qb->createNamedParameter($share->getStatus())) + ->execute(); + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) + ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) + ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) + ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) + ->set('note', $qb->createNamedParameter($share->getNote())) + ->execute(); + + /* + * Update all user defined group shares + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) + ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) + ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) + ->set('note', $qb->createNamedParameter($share->getNote())) + ->execute(); + + /* + * Now update the permissions for all children that have not set it to 0 + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0))) + ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->execute(); + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) + ->set('password', $qb->createNamedParameter($share->getPassword())) + ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL)) + ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) + ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) + ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) + ->set('token', $qb->createNamedParameter($share->getToken())) + ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) + ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('label', $qb->createNamedParameter($share->getLabel())) + ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT) + ->execute(); + } + + if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') { + $this->propagateNote($share); + } + + + return $share; + } + + /** + * Accept a share. + * + * @param IShare $share + * @param string $recipient + * @return IShare The share object + * @since 9.0.0 + */ + public function acceptShare(IShare $share, string $recipient): IShare { + if ($share->getShareType() === IShare::TYPE_GROUP) { + $group = $this->groupManager->get($share->getSharedWith()); + $user = $this->userManager->get($recipient); + + if (is_null($group)) { + throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist'); + } + + if (!$group->inGroup($user)) { + throw new ProviderException('Recipient not in receiving group'); + } + + // Try to fetch user specific share + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))) + ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->execute(); + + $data = $stmt->fetch(); + $stmt->closeCursor(); + + /* + * Check if there already is a user specific group share. + * If there is update it (if required). + */ + if ($data === false) { + $id = $this->createUserSpecificGroupShare($share, $recipient); + } else { + $id = $data['id']; + } + } elseif ($share->getShareType() === IShare::TYPE_USER) { + if ($share->getSharedWith() !== $recipient) { + throw new ProviderException('Recipient does not match'); + } + + $id = $share->getId(); + } else { + throw new ProviderException('Invalid shareType'); + } + + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED)) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->execute(); + + return $share; + } + + /** + * Get all children of this share + * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in + * + * @param \OCP\Share\IShare $parent + * @return \OCP\Share\IShare[] + */ + public function getChildren(\OCP\Share\IShare $parent) { + $children = []; + + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId()))) + ->andWhere( + $qb->expr()->in( + 'share_type', + $qb->createNamedParameter([ + IShare::TYPE_USER, + IShare::TYPE_GROUP, + IShare::TYPE_LINK, + ], IQueryBuilder::PARAM_INT_ARRAY) + ) + ) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->orderBy('id'); + + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $children[] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $children; + } + + /** + * Delete a share + * + * @param \OCP\Share\IShare $share + */ + public function delete(\OCP\Share\IShare $share) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))); + + /* + * If the share is a group share delete all possible + * user defined groups shares. + */ + if ($share->getShareType() === IShare::TYPE_GROUP) { + $qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))); + } + + $qb->execute(); + } + + /** + * Unshare a share from the recipient. If this is a group share + * this means we need a special entry in the share db. + * + * @param IShare $share + * @param string $recipient UserId of recipient + * @throws BackendError + * @throws ProviderException + */ + public function deleteFromSelf(IShare $share, $recipient) { + if ($share->getShareType() === IShare::TYPE_GROUP) { + $group = $this->groupManager->get($share->getSharedWith()); + $user = $this->userManager->get($recipient); + + if (is_null($group)) { + throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist'); + } + + if (!$group->inGroup($user)) { + throw new ProviderException('Recipient not in receiving group'); + } + + // Try to fetch user specific share + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))) + ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->execute(); + + $data = $stmt->fetch(); + + /* + * Check if there already is a user specific group share. + * If there is update it (if required). + */ + if ($data === false) { + $id = $this->createUserSpecificGroupShare($share, $recipient); + $permissions = $share->getPermissions(); + } else { + $permissions = $data['permissions']; + $id = $data['id']; + } + + if ($permissions !== 0) { + // Update existing usergroup share + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('permissions', $qb->createNamedParameter(0)) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->execute(); + } + } elseif ($share->getShareType() === IShare::TYPE_USER) { + if ($share->getSharedWith() !== $recipient) { + throw new ProviderException('Recipient does not match'); + } + + // We can just delete user and link shares + $this->delete($share); + } else { + throw new ProviderException('Invalid shareType'); + } + } + + protected function createUserSpecificGroupShare(IShare $share, string $recipient): int { + $type = $share->getNodeType(); + + $qb = $this->dbConn->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP), + 'share_with' => $qb->createNamedParameter($recipient), + 'uid_owner' => $qb->createNamedParameter($share->getShareOwner()), + 'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()), + 'parent' => $qb->createNamedParameter($share->getId()), + 'item_type' => $qb->createNamedParameter($type), + 'item_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_target' => $qb->createNamedParameter($share->getTarget()), + 'permissions' => $qb->createNamedParameter($share->getPermissions()), + 'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()), + ])->execute(); + + return $qb->getLastInsertId(); + } + + /** + * @inheritdoc + * + * For now this only works for group shares + * If this gets implemented for normal shares we have to extend it + */ + public function restore(IShare $share, string $recipient): IShare { + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('permissions') + ->from('share') + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($share->getId())) + ); + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + $originalPermission = $data['permissions']; + + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('permissions', $qb->createNamedParameter($originalPermission)) + ->where( + $qb->expr()->eq('parent', $qb->createNamedParameter($share->getParent())) + )->andWhere( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)) + )->andWhere( + $qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)) + ); + + $qb->execute(); + + return $this->getShareById($share->getId(), $recipient); + } + + /** + * @inheritdoc + */ + public function move(\OCP\Share\IShare $share, $recipient) { + if ($share->getShareType() === IShare::TYPE_USER) { + // Just update the target + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('file_target', $qb->createNamedParameter($share->getTarget())) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))) + ->execute(); + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + + // Check if there is a usergroup share + $qb = $this->dbConn->getQueryBuilder(); + $stmt = $qb->select('id') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))) + ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->setMaxResults(1) + ->execute(); + + $data = $stmt->fetch(); + $stmt->closeCursor(); + + if ($data === false) { + // No usergroup share yet. Create one. + $qb = $this->dbConn->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP), + 'share_with' => $qb->createNamedParameter($recipient), + 'uid_owner' => $qb->createNamedParameter($share->getShareOwner()), + 'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()), + 'parent' => $qb->createNamedParameter($share->getId()), + 'item_type' => $qb->createNamedParameter($share->getNodeType()), + 'item_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_target' => $qb->createNamedParameter($share->getTarget()), + 'permissions' => $qb->createNamedParameter($share->getPermissions()), + 'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()), + ])->execute(); + } else { + // Already a usergroup share. Update it. + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('file_target', $qb->createNamedParameter($share->getTarget())) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id']))) + ->execute(); + } + } + + return $share; + } + + public function getSharesInFolder($userId, Folder $node, $reshares) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('share', 's') + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $qb->andWhere($qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)) + )); + + /** + * Reshares for this user are shares where they are the owner. + */ + if ($reshares === false) { + $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); + } else { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) + ) + ); + } + + $qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid')); + $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId()))); + + $qb->orderBy('id'); + + $cursor = $qb->execute(); + $shares = []; + while ($data = $cursor->fetch()) { + $shares[$data['fileid']][] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $shares; + } + + /** + * @inheritdoc + */ + public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('share') + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType))); + + /** + * Reshares for this user are shares where they are the owner. + */ + if ($reshares === false) { + $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); + } else { + if ($node === null) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) + ) + ); + } + } + + if ($node !== null) { + $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); + } + + if ($limit !== -1) { + $qb->setMaxResults($limit); + } + + $qb->setFirstResult($offset); + $qb->orderBy('id'); + + $cursor = $qb->execute(); + $shares = []; + while ($data = $cursor->fetch()) { + $shares[] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $shares; + } + + /** + * @inheritdoc + */ + public function getShareById($id, $recipientId = null) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere( + $qb->expr()->in( + 'share_type', + $qb->createNamedParameter([ + IShare::TYPE_USER, + IShare::TYPE_GROUP, + IShare::TYPE_LINK, + ], IQueryBuilder::PARAM_INT_ARRAY) + ) + ) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ShareNotFound(); + } + + try { + $share = $this->createShare($data); + } catch (InvalidShare $e) { + throw new ShareNotFound(); + } + + // If the recipient is set for a group share resolve to that user + if ($recipientId !== null && $share->getShareType() === IShare::TYPE_GROUP) { + $share = $this->resolveGroupShares([$share], $recipientId)[0]; + } + + return $share; + } + + /** + * Get shares for a given path + * + * @param \OCP\Files\Node $path + * @return \OCP\Share\IShare[] + */ + public function getSharesByPath(Node $path) { + $qb = $this->dbConn->getQueryBuilder(); + + $cursor = $qb->select('*') + ->from('share') + ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId()))) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)) + ) + ) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->execute(); + + $shares = []; + while ($data = $cursor->fetch()) { + $shares[] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $shares; + } + + /** + * Returns whether the given database result can be interpreted as + * a share with accessible file (not trashed, not deleted) + */ + private function isAccessibleResult($data) { + // exclude shares leading to deleted file entries + if ($data['fileid'] === null || $data['path'] === null) { + return false; + } + + // exclude shares leading to trashbin on home storages + $pathSections = explode('/', $data['path'], 2); + // FIXME: would not detect rare md5'd home storage case properly + if ($pathSections[0] !== 'files' + && in_array(explode(':', $data['storage_string_id'], 2)[0], ['home', 'object'])) { + return false; + } + return true; + } + + /** + * @inheritdoc + */ + public function getSharedWith($userId, $shareType, $node, $limit, $offset) { + /** @var Share[] $shares */ + $shares = []; + + if ($shareType === IShare::TYPE_USER) { + //Get shares directly with this user + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('s.*', + 'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash', + 'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime', + 'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum' + ) + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's') + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')); + + // Order by id + $qb->orderBy('s.id'); + + // Set limit and offset + if ($limit !== -1) { + $qb->setMaxResults($limit); + } + $qb->setFirstResult($offset); + + $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + // Filter by node if provided + if ($node !== null) { + $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); + } + + $cursor = $qb->execute(); + + while ($data = $cursor->fetch()) { + if ($this->isAccessibleResult($data)) { + $shares[] = $this->createShare($data); + } + } + $cursor->closeCursor(); + } elseif ($shareType === IShare::TYPE_GROUP) { + $user = $this->userManager->get($userId); + $allGroups = ($user instanceof IUser) ? $this->groupManager->getUserGroupIds($user) : []; + + /** @var Share[] $shares2 */ + $shares2 = []; + + $start = 0; + while (true) { + $groups = array_slice($allGroups, $start, 100); + $start += 100; + + if ($groups === []) { + break; + } + + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('s.*', + 'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash', + 'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime', + 'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum' + ) + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's') + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')) + ->orderBy('s.id') + ->setFirstResult(0); + + if ($limit !== -1) { + $qb->setMaxResults($limit - count($shares)); + } + + // Filter by node if provided + if ($node !== null) { + $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId()))); + } + + + $groups = array_filter($groups); + + $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))) + ->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter( + $groups, + IQueryBuilder::PARAM_STR_ARRAY + ))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + if ($offset > 0) { + $offset--; + continue; + } + + if ($this->isAccessibleResult($data)) { + $shares2[] = $this->createShare($data); + } + } + $cursor->closeCursor(); + } + + /* + * Resolve all group shares to user specific shares + */ + $shares = $this->resolveGroupShares($shares2, $userId); + } else { + throw new BackendError('Invalid backend'); + } + + + return $shares; + } + + /** + * Get a share by token + * + * @param string $token + * @return \OCP\Share\IShare + * @throws ShareNotFound + */ + public function getShareByToken($token) { + $qb = $this->dbConn->getQueryBuilder(); + + $cursor = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK))) + ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->execute(); + + $data = $cursor->fetch(); + + if ($data === false) { + throw new ShareNotFound(); + } + + try { + $share = $this->createShare($data); + } catch (InvalidShare $e) { + throw new ShareNotFound(); + } + + return $share; + } + + /** + * Create a share object from an database row + * + * @param mixed[] $data + * @return \OCP\Share\IShare + * @throws InvalidShare + */ + private function createShare($data) { + $share = new Share($this->rootFolder, $this->userManager); + $share->setId((int)$data['id']) + ->setShareType((int)$data['share_type']) + ->setPermissions((int)$data['permissions']) + ->setTarget($data['file_target']) + ->setNote($data['note']) + ->setMailSend((bool)$data['mail_send']) + ->setStatus((int)$data['accepted']) + ->setLabel($data['label']); + + $shareTime = new \DateTime(); + $shareTime->setTimestamp((int)$data['stime']); + $share->setShareTime($shareTime); + + if ($share->getShareType() === IShare::TYPE_USER) { + $share->setSharedWith($data['share_with']); + $user = $this->userManager->get($data['share_with']); + if ($user !== null) { + $share->setSharedWithDisplayName($user->getDisplayName()); + } + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $share->setSharedWith($data['share_with']); + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + $share->setPassword($data['password']); + $share->setSendPasswordByTalk((bool)$data['password_by_talk']); + $share->setToken($data['token']); + } + + $share->setSharedBy($data['uid_initiator']); + $share->setShareOwner($data['uid_owner']); + + $share->setNodeId((int)$data['file_source']); + $share->setNodeType($data['item_type']); + + if ($data['expiration'] !== null) { + $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']); + $share->setExpirationDate($expiration); + } + + if (isset($data['f_permissions'])) { + $entryData = $data; + $entryData['permissions'] = $entryData['f_permissions']; + $entryData['parent'] = $entryData['f_parent']; + $share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData, + \OC::$server->getMimeTypeLoader())); + } + + $share->setProviderId($this->identifier()); + $share->setHideDownload((int)$data['hide_download'] === 1); + + return $share; + } + + /** + * @param Share[] $shares + * @param $userId + * @return Share[] The updates shares if no update is found for a share return the original + */ + private function resolveGroupShares($shares, $userId) { + $result = []; + + $start = 0; + while (true) { + /** @var Share[] $shareSlice */ + $shareSlice = array_slice($shares, $start, 100); + $start += 100; + + if ($shareSlice === []) { + break; + } + + /** @var int[] $ids */ + $ids = []; + /** @var Share[] $shareMap */ + $shareMap = []; + + foreach ($shareSlice as $share) { + $ids[] = (int)$share->getId(); + $shareMap[$share->getId()] = $share; + } + + $qb = $this->dbConn->getQueryBuilder(); + + $query = $qb->select('*') + ->from('share') + ->where($qb->expr()->in('parent', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $stmt = $query->execute(); + + while ($data = $stmt->fetch()) { + $shareMap[$data['parent']]->setPermissions((int)$data['permissions']); + $shareMap[$data['parent']]->setStatus((int)$data['accepted']); + $shareMap[$data['parent']]->setTarget($data['file_target']); + $shareMap[$data['parent']]->setParent($data['parent']); + } + + $stmt->closeCursor(); + + foreach ($shareMap as $share) { + $result[] = $share; + } + } + + return $result; + } + + /** + * A user is deleted from the system + * So clean up the relevant shares. + * + * @param string $uid + * @param int $shareType + */ + public function userDeleted($uid, $shareType) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->delete('share'); + + if ($shareType === IShare::TYPE_USER) { + /* + * Delete all user shares that are owned by this user + * or that are received by this user + */ + + $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER))); + + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)), + $qb->expr()->eq('share_with', $qb->createNamedParameter($uid)) + ) + ); + } elseif ($shareType === IShare::TYPE_GROUP) { + /* + * Delete all group shares that are owned by this user + * Or special user group shares that are received by this user + */ + $qb->where( + $qb->expr()->andX( + $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)) + ), + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)) + ) + ); + + $qb->orWhere( + $qb->expr()->andX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)), + $qb->expr()->eq('share_with', $qb->createNamedParameter($uid)) + ) + ); + } elseif ($shareType === IShare::TYPE_LINK) { + /* + * Delete all link shares owned by this user. + * And all link shares initiated by this user (until #22327 is in) + */ + $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK))); + + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)), + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($uid)) + ) + ); + } else { + \OC::$server->getLogger()->logException(new \InvalidArgumentException('Default share provider tried to delete all shares for type: ' . $shareType)); + return; + } + + $qb->execute(); + } + + /** + * Delete all shares received by this group. As well as any custom group + * shares for group members. + * + * @param string $gid + */ + public function groupDeleted($gid) { + /* + * First delete all custom group shares for group members + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('id') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid))); + + $cursor = $qb->execute(); + $ids = []; + while ($row = $cursor->fetch()) { + $ids[] = (int)$row['id']; + } + $cursor->closeCursor(); + + if (!empty($ids)) { + $chunks = array_chunk($ids, 100); + foreach ($chunks as $chunk) { + $qb->delete('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + $qb->execute(); + } + } + + /* + * Now delete all the group shares + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid))); + $qb->execute(); + } + + /** + * Delete custom group shares to this group for this user + * + * @param string $uid + * @param string $gid + */ + public function userDeletedFromGroup($uid, $gid) { + /* + * Get all group shares + */ + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('id') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid))); + + $cursor = $qb->execute(); + $ids = []; + while ($row = $cursor->fetch()) { + $ids[] = (int)$row['id']; + } + $cursor->closeCursor(); + + if (!empty($ids)) { + $chunks = array_chunk($ids, 100); + foreach ($chunks as $chunk) { + /* + * Delete all special shares wit this users for the found group shares + */ + $qb->delete('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + $qb->execute(); + } + } + } + + /** + * @inheritdoc + */ + public function getAccessList($nodes, $currentAccess) { + $ids = []; + foreach ($nodes as $node) { + $ids[] = $node->getId(); + } + + $qb = $this->dbConn->getQueryBuilder(); + + $or = $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)) + ); + + if ($currentAccess) { + $or->add($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))); + } + + $qb->select('id', 'parent', 'share_type', 'share_with', 'file_source', 'file_target', 'permissions') + ->from('share') + ->where( + $or + ) + ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + $cursor = $qb->execute(); + + $users = []; + $link = false; + while ($row = $cursor->fetch()) { + $type = (int)$row['share_type']; + if ($type === IShare::TYPE_USER) { + $uid = $row['share_with']; + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } elseif ($type === IShare::TYPE_GROUP) { + $gid = $row['share_with']; + $group = $this->groupManager->get($gid); + + if ($group === null) { + continue; + } + + $userList = $group->getUsers(); + foreach ($userList as $user) { + $uid = $user->getUID(); + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } + } elseif ($type === IShare::TYPE_LINK) { + $link = true; + } elseif ($type === IShare::TYPE_USERGROUP && $currentAccess === true) { + $uid = $row['share_with']; + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } + } + $cursor->closeCursor(); + + if ($currentAccess === true) { + $users = array_map([$this, 'filterSharesOfUser'], $users); + $users = array_filter($users); + } else { + $users = array_keys($users); + } + + return ['users' => $users, 'public' => $link]; + } + + /** + * For each user the path with the fewest slashes is returned + * @param array $shares + * @return array + */ + protected function filterSharesOfUser(array $shares) { + // Group shares when the user has a share exception + foreach ($shares as $id => $share) { + $type = (int) $share['share_type']; + $permissions = (int) $share['permissions']; + + if ($type === IShare::TYPE_USERGROUP) { + unset($shares[$share['parent']]); + + if ($permissions === 0) { + unset($shares[$id]); + } + } + } + + $best = []; + $bestDepth = 0; + foreach ($shares as $id => $share) { + $depth = substr_count($share['file_target'], '/'); + if (empty($best) || $depth < $bestDepth) { + $bestDepth = $depth; + $best = [ + 'node_id' => $share['file_source'], + 'node_path' => $share['file_target'], + ]; + } + } + + return $best; + } + + /** + * propagate notes to the recipients + * + * @param IShare $share + * @throws \OCP\Files\NotFoundException + */ + private function propagateNote(IShare $share) { + if ($share->getShareType() === IShare::TYPE_USER) { + $user = $this->userManager->get($share->getSharedWith()); + $this->sendNote([$user], $share); + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $group = $this->groupManager->get($share->getSharedWith()); + $groupMembers = $group->getUsers(); + $this->sendNote($groupMembers, $share); + } + } + + /** + * send note by mail + * + * @param array $recipients + * @param IShare $share + * @throws \OCP\Files\NotFoundException + */ + private function sendNote(array $recipients, IShare $share) { + $toListByLanguage = []; + + foreach ($recipients as $recipient) { + /** @var IUser $recipient */ + $email = $recipient->getEMailAddress(); + if ($email) { + $language = $this->l10nFactory->getUserLanguage($recipient); + if (!isset($toListByLanguage[$language])) { + $toListByLanguage[$language] = []; + } + $toListByLanguage[$language][$email] = $recipient->getDisplayName(); + } + } + + if (empty($toListByLanguage)) { + return; + } + + foreach ($toListByLanguage as $l10n => $toList) { + $filename = $share->getNode()->getName(); + $initiator = $share->getSharedBy(); + $note = $share->getNote(); + + $l = $this->l10nFactory->get('lib', $l10n); + + $initiatorUser = $this->userManager->get($initiator); + $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; + $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null; + $plainHeading = $l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]); + $htmlHeading = $l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]); + $message = $this->mailer->createMessage(); + + $emailTemplate = $this->mailer->createEMailTemplate('defaultShareProvider.sendNote'); + + $emailTemplate->setSubject($l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName])); + $emailTemplate->addHeader(); + $emailTemplate->addHeading($htmlHeading, $plainHeading); + $emailTemplate->addBodyText(htmlspecialchars($note), $note); + + $link = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]); + $emailTemplate->addBodyButton( + $l->t('Open »%s«', [$filename]), + $link + ); + + + // The "From" contains the sharers name + $instanceName = $this->defaults->getName(); + $senderName = $l->t( + '%1$s via %2$s', + [ + $initiatorDisplayName, + $instanceName + ] + ); + $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); + if ($initiatorEmailAddress !== null) { + $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]); + $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan()); + } else { + $emailTemplate->addFooter(); + } + + if (count($toList) === 1) { + $message->setTo($toList); + } else { + $message->setTo([]); + $message->setBcc($toList); + } + $message->useTemplate($emailTemplate); + $this->mailer->send($message); + } + } + + public function getAllShares(): iterable { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->select('*') + ->from('share') + ->where( + $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_GROUP)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_LINK)) + ) + ); + + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + try { + $share = $this->createShare($data); + } catch (InvalidShare $e) { + continue; + } + + yield $share; + } + $cursor->closeCursor(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Exception/BackendError.php b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/BackendError.php new file mode 100644 index 0000000..5ba17b7 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/BackendError.php @@ -0,0 +1,26 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20\Exception; + +class BackendError extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Exception/InvalidShare.php b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/InvalidShare.php new file mode 100644 index 0000000..1216bfa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/InvalidShare.php @@ -0,0 +1,26 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20\Exception; + +class InvalidShare extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Exception/ProviderException.php b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/ProviderException.php new file mode 100644 index 0000000..f60f5a8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Exception/ProviderException.php @@ -0,0 +1,26 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20\Exception; + +class ProviderException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Hooks.php b/docker/overlays/nextcloud/html/lib/private/Share20/Hooks.php new file mode 100644 index 0000000..b596123 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Hooks.php @@ -0,0 +1,33 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20; + +class Hooks { + public static function post_deleteUser($arguments) { + \OC::$server->getShareManager()->userDeleted($arguments['uid']); + } + + public static function post_deleteGroup($arguments) { + \OC::$server->getShareManager()->groupDeleted($arguments['gid']); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/LegacyHooks.php b/docker/overlays/nextcloud/html/lib/private/Share20/LegacyHooks.php new file mode 100644 index 0000000..f657b83 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/LegacyHooks.php @@ -0,0 +1,178 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author Morris Jobke + * @author Pauli Järvinen + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Share20; + +use OCP\Files\File; +use OCP\Share; +use OCP\Share\IShare; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class LegacyHooks { + + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** + * LegacyHooks constructor. + * + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct(EventDispatcherInterface $eventDispatcher) { + $this->eventDispatcher = $eventDispatcher; + + $this->eventDispatcher->addListener('OCP\Share::preUnshare', [$this, 'preUnshare']); + $this->eventDispatcher->addListener('OCP\Share::postUnshare', [$this, 'postUnshare']); + $this->eventDispatcher->addListener('OCP\Share::postUnshareFromSelf', [$this, 'postUnshareFromSelf']); + $this->eventDispatcher->addListener('OCP\Share::preShare', [$this, 'preShare']); + $this->eventDispatcher->addListener('OCP\Share::postShare', [$this, 'postShare']); + } + + /** + * @param GenericEvent $e + */ + public function preUnshare(GenericEvent $e) { + /** @var IShare $share */ + $share = $e->getSubject(); + + $formatted = $this->formatHookParams($share); + \OC_Hook::emit(Share::class, 'pre_unshare', $formatted); + } + + /** + * @param GenericEvent $e + */ + public function postUnshare(GenericEvent $e) { + /** @var IShare $share */ + $share = $e->getSubject(); + + $formatted = $this->formatHookParams($share); + + /** @var IShare[] $deletedShares */ + $deletedShares = $e->getArgument('deletedShares'); + + $formattedDeletedShares = array_map(function ($share) { + return $this->formatHookParams($share); + }, $deletedShares); + + $formatted['deletedShares'] = $formattedDeletedShares; + + \OC_Hook::emit(Share::class, 'post_unshare', $formatted); + } + + /** + * @param GenericEvent $e + */ + public function postUnshareFromSelf(GenericEvent $e) { + /** @var IShare $share */ + $share = $e->getSubject(); + + $formatted = $this->formatHookParams($share); + $formatted['itemTarget'] = $formatted['fileTarget']; + $formatted['unsharedItems'] = [$formatted]; + + \OC_Hook::emit(Share::class, 'post_unshareFromSelf', $formatted); + } + + private function formatHookParams(IShare $share) { + // Prepare hook + $shareType = $share->getShareType(); + $sharedWith = ''; + if ($shareType === IShare::TYPE_USER || + $shareType === IShare::TYPE_GROUP || + $shareType === IShare::TYPE_REMOTE) { + $sharedWith = $share->getSharedWith(); + } + + $hookParams = [ + 'id' => $share->getId(), + 'itemType' => $share->getNodeType(), + 'itemSource' => $share->getNodeId(), + 'shareType' => $shareType, + 'shareWith' => $sharedWith, + 'itemparent' => method_exists($share, 'getParent') ? $share->getParent() : '', + 'uidOwner' => $share->getSharedBy(), + 'fileSource' => $share->getNodeId(), + 'fileTarget' => $share->getTarget() + ]; + return $hookParams; + } + + public function preShare(GenericEvent $e) { + /** @var IShare $share */ + $share = $e->getSubject(); + + // Pre share hook + $run = true; + $error = ''; + $preHookData = [ + 'itemType' => $share->getNode() instanceof File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getNode()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'itemTarget' => $share->getTarget(), + 'shareWith' => $share->getSharedWith(), + 'run' => &$run, + 'error' => &$error, + ]; + \OC_Hook::emit(Share::class, 'pre_shared', $preHookData); + + if ($run === false) { + $e->setArgument('error', $error); + $e->stopPropagation(); + } + + return $e; + } + + public function postShare(GenericEvent $e) { + /** @var IShare $share */ + $share = $e->getSubject(); + + $postHookData = [ + 'itemType' => $share->getNode() instanceof File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getNode()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'id' => $share->getId(), + 'shareWith' => $share->getSharedWith(), + 'itemTarget' => $share->getTarget(), + 'fileTarget' => $share->getTarget(), + ]; + + \OC_Hook::emit(Share::class, 'post_shared', $postHookData); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Manager.php b/docker/overlays/nextcloud/html/lib/private/Share20/Manager.php new file mode 100644 index 0000000..1b1818a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Manager.php @@ -0,0 +1,1898 @@ + + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Daniel Kesselberg + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Lukas Reschke + * @author Maxence Lange + * @author Maxence Lange + * @author Morris Jobke + * @author Pauli Järvinen + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20; + +use OC\Cache\CappedMemoryCache; +use OC\Files\Mount\MoveableMount; +use OC\HintException; +use OC\Share20\Exception\ProviderException; +use OCA\Files_Sharing\ISharedStorage; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Node; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\L10N\IFactory; +use OCP\Mail\IMailer; +use OCP\Security\Events\ValidatePasswordPolicyEvent; +use OCP\Security\IHasher; +use OCP\Security\ISecureRandom; +use OCP\Share; +use OCP\Share\Exceptions\GenericShareException; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager; +use OCP\Share\IProviderFactory; +use OCP\Share\IShare; +use OCP\Share\IShareProvider; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * This class is the communication hub for all sharing related operations. + */ +class Manager implements IManager { + + /** @var IProviderFactory */ + private $factory; + /** @var ILogger */ + private $logger; + /** @var IConfig */ + private $config; + /** @var ISecureRandom */ + private $secureRandom; + /** @var IHasher */ + private $hasher; + /** @var IMountManager */ + private $mountManager; + /** @var IGroupManager */ + private $groupManager; + /** @var IL10N */ + private $l; + /** @var IFactory */ + private $l10nFactory; + /** @var IUserManager */ + private $userManager; + /** @var IRootFolder */ + private $rootFolder; + /** @var CappedMemoryCache */ + private $sharingDisabledForUsersCache; + /** @var EventDispatcherInterface */ + private $legacyDispatcher; + /** @var LegacyHooks */ + private $legacyHooks; + /** @var IMailer */ + private $mailer; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var \OC_Defaults */ + private $defaults; + /** @var IEventDispatcher */ + private $dispatcher; + + + /** + * Manager constructor. + * + * @param ILogger $logger + * @param IConfig $config + * @param ISecureRandom $secureRandom + * @param IHasher $hasher + * @param IMountManager $mountManager + * @param IGroupManager $groupManager + * @param IL10N $l + * @param IFactory $l10nFactory + * @param IProviderFactory $factory + * @param IUserManager $userManager + * @param IRootFolder $rootFolder + * @param EventDispatcherInterface $eventDispatcher + * @param IMailer $mailer + * @param IURLGenerator $urlGenerator + * @param \OC_Defaults $defaults + */ + public function __construct( + ILogger $logger, + IConfig $config, + ISecureRandom $secureRandom, + IHasher $hasher, + IMountManager $mountManager, + IGroupManager $groupManager, + IL10N $l, + IFactory $l10nFactory, + IProviderFactory $factory, + IUserManager $userManager, + IRootFolder $rootFolder, + EventDispatcherInterface $legacyDispatcher, + IMailer $mailer, + IURLGenerator $urlGenerator, + \OC_Defaults $defaults, + IEventDispatcher $dispatcher + ) { + $this->logger = $logger; + $this->config = $config; + $this->secureRandom = $secureRandom; + $this->hasher = $hasher; + $this->mountManager = $mountManager; + $this->groupManager = $groupManager; + $this->l = $l; + $this->l10nFactory = $l10nFactory; + $this->factory = $factory; + $this->userManager = $userManager; + $this->rootFolder = $rootFolder; + $this->legacyDispatcher = $legacyDispatcher; + $this->sharingDisabledForUsersCache = new CappedMemoryCache(); + $this->legacyHooks = new LegacyHooks($this->legacyDispatcher); + $this->mailer = $mailer; + $this->urlGenerator = $urlGenerator; + $this->defaults = $defaults; + $this->dispatcher = $dispatcher; + } + + /** + * Convert from a full share id to a tuple (providerId, shareId) + * + * @param string $id + * @return string[] + */ + private function splitFullId($id) { + return explode(':', $id, 2); + } + + /** + * Verify if a password meets all requirements + * + * @param string $password + * @throws \Exception + */ + protected function verifyPassword($password) { + if ($password === null) { + // No password is set, check if this is allowed. + if ($this->shareApiLinkEnforcePassword()) { + throw new \InvalidArgumentException('Passwords are enforced for link shares'); + } + + return; + } + + // Let others verify the password + try { + $this->legacyDispatcher->dispatch(new ValidatePasswordPolicyEvent($password)); + } catch (HintException $e) { + throw new \Exception($e->getHint()); + } + } + + /** + * Check for generic requirements before creating a share + * + * @param IShare $share + * @throws \InvalidArgumentException + * @throws GenericShareException + * + * @suppress PhanUndeclaredClassMethod + */ + protected function generalCreateChecks(IShare $share) { + if ($share->getShareType() === IShare::TYPE_USER) { + // We expect a valid user as sharedWith for user shares + if (!$this->userManager->userExists($share->getSharedWith())) { + throw new \InvalidArgumentException('SharedWith is not a valid user'); + } + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + // We expect a valid group as sharedWith for group shares + if (!$this->groupManager->groupExists($share->getSharedWith())) { + throw new \InvalidArgumentException('SharedWith is not a valid group'); + } + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + if ($share->getSharedWith() !== null) { + throw new \InvalidArgumentException('SharedWith should be empty'); + } + } elseif ($share->getShareType() === IShare::TYPE_REMOTE) { + if ($share->getSharedWith() === null) { + throw new \InvalidArgumentException('SharedWith should not be empty'); + } + } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) { + if ($share->getSharedWith() === null) { + throw new \InvalidArgumentException('SharedWith should not be empty'); + } + } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { + if ($share->getSharedWith() === null) { + throw new \InvalidArgumentException('SharedWith should not be empty'); + } + } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) { + $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith()); + if ($circle === null) { + throw new \InvalidArgumentException('SharedWith is not a valid circle'); + } + } elseif ($share->getShareType() === IShare::TYPE_ROOM) { + } else { + // We can't handle other types yet + throw new \InvalidArgumentException('unknown share type'); + } + + // Verify the initiator of the share is set + if ($share->getSharedBy() === null) { + throw new \InvalidArgumentException('SharedBy should be set'); + } + + // Cannot share with yourself + if ($share->getShareType() === IShare::TYPE_USER && + $share->getSharedWith() === $share->getSharedBy()) { + throw new \InvalidArgumentException('Can’t share with yourself'); + } + + // The path should be set + if ($share->getNode() === null) { + throw new \InvalidArgumentException('Path should be set'); + } + + // And it should be a file or a folder + if (!($share->getNode() instanceof \OCP\Files\File) && + !($share->getNode() instanceof \OCP\Files\Folder)) { + throw new \InvalidArgumentException('Path should be either a file or a folder'); + } + + // And you can't share your rootfolder + if ($this->userManager->userExists($share->getSharedBy())) { + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); + } else { + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + } + if ($userFolder->getId() === $share->getNode()->getId()) { + throw new \InvalidArgumentException('You can’t share your root folder'); + } + + // Check if we actually have share permissions + if (!$share->getNode()->isShareable()) { + $path = $userFolder->getRelativePath($share->getNode()->getPath()); + $message_t = $this->l->t('You are not allowed to share %s', [$path]); + throw new GenericShareException($message_t, $message_t, 404); + } + + // Permissions should be set + if ($share->getPermissions() === null) { + throw new \InvalidArgumentException('A share requires permissions'); + } + + $isFederatedShare = $share->getNode()->getStorage()->instanceOfStorage('\OCA\Files_Sharing\External\Storage'); + $permissions = 0; + + if (!$isFederatedShare && $share->getNode()->getOwner() && $share->getNode()->getOwner()->getUID() !== $share->getSharedBy()) { + $userMounts = array_filter($userFolder->getById($share->getNode()->getId()), function ($mount) { + // We need to filter since there might be other mountpoints that contain the file + // e.g. if the user has access to the same external storage that the file is originating from + return $mount->getStorage()->instanceOfStorage(ISharedStorage::class); + }); + $userMount = array_shift($userMounts); + if ($userMount === null) { + throw new GenericShareException('Could not get proper share mount for ' . $share->getNode()->getId() . '. Failing since else the next calls are called with null'); + } + $mount = $userMount->getMountPoint(); + // When it's a reshare use the parent share permissions as maximum + $userMountPointId = $mount->getStorageRootId(); + $userMountPoints = $userFolder->getById($userMountPointId); + $userMountPoint = array_shift($userMountPoints); + + if ($userMountPoint === null) { + throw new GenericShareException('Could not get proper user mount for ' . $userMountPointId . '. Failing since else the next calls are called with null'); + } + + /* Check if this is an incoming share */ + $incomingShares = $this->getSharedWith($share->getSharedBy(), IShare::TYPE_USER, $userMountPoint, -1, 0); + $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_GROUP, $userMountPoint, -1, 0)); + $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_CIRCLE, $userMountPoint, -1, 0)); + $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_ROOM, $userMountPoint, -1, 0)); + + /** @var IShare[] $incomingShares */ + if (!empty($incomingShares)) { + foreach ($incomingShares as $incomingShare) { + $permissions |= $incomingShare->getPermissions(); + } + } + } else { + /* + * Quick fix for #23536 + * Non moveable mount points do not have update and delete permissions + * while we 'most likely' do have that on the storage. + */ + $permissions = $share->getNode()->getPermissions(); + if (!($share->getNode()->getMountPoint() instanceof MoveableMount)) { + $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; + } + } + + // Check that we do not share with more permissions than we have + if ($share->getPermissions() & ~$permissions) { + $path = $userFolder->getRelativePath($share->getNode()->getPath()); + $message_t = $this->l->t('Can’t increase permissions of %s', [$path]); + throw new GenericShareException($message_t, $message_t, 404); + } + + + // Check that read permissions are always set + // Link shares are allowed to have no read permissions to allow upload to hidden folders + $noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK + || $share->getShareType() === IShare::TYPE_EMAIL; + if (!$noReadPermissionRequired && + ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { + throw new \InvalidArgumentException('Shares need at least read permissions'); + } + + if ($share->getNode() instanceof \OCP\Files\File) { + if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) { + $message_t = $this->l->t('Files can’t be shared with delete permissions'); + throw new GenericShareException($message_t); + } + if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) { + $message_t = $this->l->t('Files can’t be shared with create permissions'); + throw new GenericShareException($message_t); + } + } + } + + /** + * Validate if the expiration date fits the system settings + * + * @param IShare $share The share to validate the expiration date of + * @return IShare The modified share object + * @throws GenericShareException + * @throws \InvalidArgumentException + * @throws \Exception + */ + protected function validateExpirationDateInternal(IShare $share) { + $expirationDate = $share->getExpirationDate(); + + if ($expirationDate !== null) { + //Make sure the expiration date is a date + $expirationDate->setTime(0, 0, 0); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + if ($date >= $expirationDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new GenericShareException($message, $message, 404); + } + } + + // If expiredate is empty set a default one if there is a default + $fullId = null; + try { + $fullId = $share->getFullId(); + } catch (\UnexpectedValueException $e) { + // This is a new share + } + + if ($fullId === null && $expirationDate === null && $this->shareApiInternalDefaultExpireDate()) { + $expirationDate = new \DateTime(); + $expirationDate->setTime(0,0,0); + + $days = (int)$this->config->getAppValue('core', 'internal_defaultExpDays', $this->shareApiLinkDefaultExpireDays()); + if ($days > $this->shareApiLinkDefaultExpireDays()) { + $days = $this->shareApiLinkDefaultExpireDays(); + } + $expirationDate->add(new \DateInterval('P'.$days.'D')); + } + + // If we enforce the expiration date check that is does not exceed + if ($this->shareApiInternalDefaultExpireDateEnforced()) { + if ($expirationDate === null) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiInternalDefaultExpireDays() . 'D')); + if ($date < $expirationDate) { + $message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiInternalDefaultExpireDays()]); + throw new GenericShareException($message, $message, 404); + } + } + + $accepted = true; + $message = ''; + \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [ + 'expirationDate' => &$expirationDate, + 'accepted' => &$accepted, + 'message' => &$message, + 'passwordSet' => $share->getPassword() !== null, + ]); + + if (!$accepted) { + throw new \Exception($message); + } + + $share->setExpirationDate($expirationDate); + + return $share; + } + + /** + * Validate if the expiration date fits the system settings + * + * @param IShare $share The share to validate the expiration date of + * @return IShare The modified share object + * @throws GenericShareException + * @throws \InvalidArgumentException + * @throws \Exception + */ + protected function validateExpirationDate(IShare $share) { + $expirationDate = $share->getExpirationDate(); + + if ($expirationDate !== null) { + //Make sure the expiration date is a date + $expirationDate->setTime(0, 0, 0); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + if ($date >= $expirationDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new GenericShareException($message, $message, 404); + } + } + + // If expiredate is empty set a default one if there is a default + $fullId = null; + try { + $fullId = $share->getFullId(); + } catch (\UnexpectedValueException $e) { + // This is a new share + } + + if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) { + $expirationDate = new \DateTime(); + $expirationDate->setTime(0,0,0); + + $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', $this->shareApiLinkDefaultExpireDays()); + if ($days > $this->shareApiLinkDefaultExpireDays()) { + $days = $this->shareApiLinkDefaultExpireDays(); + } + $expirationDate->add(new \DateInterval('P'.$days.'D')); + } + + // If we enforce the expiration date check that is does not exceed + if ($this->shareApiLinkDefaultExpireDateEnforced()) { + if ($expirationDate === null) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D')); + if ($date < $expirationDate) { + $message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]); + throw new GenericShareException($message, $message, 404); + } + } + + $accepted = true; + $message = ''; + \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [ + 'expirationDate' => &$expirationDate, + 'accepted' => &$accepted, + 'message' => &$message, + 'passwordSet' => $share->getPassword() !== null, + ]); + + if (!$accepted) { + throw new \Exception($message); + } + + $share->setExpirationDate($expirationDate); + + return $share; + } + + /** + * Check for pre share requirements for user shares + * + * @param IShare $share + * @throws \Exception + */ + protected function userCreateChecks(IShare $share) { + // Check if we can share with group members only + if ($this->shareWithGroupMembersOnly()) { + $sharedBy = $this->userManager->get($share->getSharedBy()); + $sharedWith = $this->userManager->get($share->getSharedWith()); + // Verify we can share with this user + $groups = array_intersect( + $this->groupManager->getUserGroupIds($sharedBy), + $this->groupManager->getUserGroupIds($sharedWith) + ); + if (empty($groups)) { + throw new \Exception('Sharing is only allowed with group members'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $provider = $this->factory->getProviderForType(IShare::TYPE_USER); + $existingShares = $provider->getSharesByPath($share->getNode()); + foreach ($existingShares as $existingShare) { + // Ignore if it is the same share + try { + if ($existingShare->getFullId() === $share->getFullId()) { + continue; + } + } catch (\UnexpectedValueException $e) { + //Shares are not identical + } + + // Identical share already existst + if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) { + throw new \Exception('Path is already shared with this user'); + } + + // The share is already shared with this user via a group share + if ($existingShare->getShareType() === IShare::TYPE_GROUP) { + $group = $this->groupManager->get($existingShare->getSharedWith()); + if (!is_null($group)) { + $user = $this->userManager->get($share->getSharedWith()); + + if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) { + throw new \Exception('Path is already shared with this user'); + } + } + } + } + } + + /** + * Check for pre share requirements for group shares + * + * @param IShare $share + * @throws \Exception + */ + protected function groupCreateChecks(IShare $share) { + // Verify group shares are allowed + if (!$this->allowGroupSharing()) { + throw new \Exception('Group sharing is now allowed'); + } + + // Verify if the user can share with this group + if ($this->shareWithGroupMembersOnly()) { + $sharedBy = $this->userManager->get($share->getSharedBy()); + $sharedWith = $this->groupManager->get($share->getSharedWith()); + if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) { + throw new \Exception('Sharing is only allowed within your own groups'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP); + $existingShares = $provider->getSharesByPath($share->getNode()); + foreach ($existingShares as $existingShare) { + try { + if ($existingShare->getFullId() === $share->getFullId()) { + continue; + } + } catch (\UnexpectedValueException $e) { + //It is a new share so just continue + } + + if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) { + throw new \Exception('Path is already shared with this group'); + } + } + } + + /** + * Check for pre share requirements for link shares + * + * @param IShare $share + * @throws \Exception + */ + protected function linkCreateChecks(IShare $share) { + // Are link shares allowed? + if (!$this->shareApiAllowLinks()) { + throw new \Exception('Link sharing is not allowed'); + } + + // Check if public upload is allowed + if (!$this->shareApiLinkAllowPublicUpload() && + ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) { + throw new \InvalidArgumentException('Public upload is not allowed'); + } + } + + /** + * To make sure we don't get invisible link shares we set the parent + * of a link if it is a reshare. This is a quick word around + * until we can properly display multiple link shares in the UI + * + * See: https://github.com/owncloud/core/issues/22295 + * + * FIXME: Remove once multiple link shares can be properly displayed + * + * @param IShare $share + */ + protected function setLinkParent(IShare $share) { + + // No sense in checking if the method is not there. + if (method_exists($share, 'setParent')) { + $storage = $share->getNode()->getStorage(); + if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) { + /** @var \OCA\Files_Sharing\SharedStorage $storage */ + $share->setParent($storage->getShareId()); + } + } + } + + /** + * @param File|Folder $path + */ + protected function pathCreateChecks($path) { + // Make sure that we do not share a path that contains a shared mountpoint + if ($path instanceof \OCP\Files\Folder) { + $mounts = $this->mountManager->findIn($path->getPath()); + foreach ($mounts as $mount) { + if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) { + throw new \InvalidArgumentException('Path contains files shared with you'); + } + } + } + } + + /** + * Check if the user that is sharing can actually share + * + * @param IShare $share + * @throws \Exception + */ + protected function canShare(IShare $share) { + if (!$this->shareApiEnabled()) { + throw new \Exception('Sharing is disabled'); + } + + if ($this->sharingDisabledForUser($share->getSharedBy())) { + throw new \Exception('Sharing is disabled for you'); + } + } + + /** + * Share a path + * + * @param IShare $share + * @return IShare The share object + * @throws \Exception + * + * TODO: handle link share permissions or check them + */ + public function createShare(IShare $share) { + $this->canShare($share); + + $this->generalCreateChecks($share); + + // Verify if there are any issues with the path + $this->pathCreateChecks($share->getNode()); + + /* + * On creation of a share the owner is always the owner of the path + * Except for mounted federated shares. + */ + $storage = $share->getNode()->getStorage(); + if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { + $parent = $share->getNode()->getParent(); + while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { + $parent = $parent->getParent(); + } + $share->setShareOwner($parent->getOwner()->getUID()); + } else { + if ($share->getNode()->getOwner()) { + $share->setShareOwner($share->getNode()->getOwner()->getUID()); + } else { + $share->setShareOwner($share->getSharedBy()); + } + } + + //Verify share type + if ($share->getShareType() === IShare::TYPE_USER) { + $this->userCreateChecks($share); + + //Verify the expiration date + $share = $this->validateExpirationDateInternal($share); + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $this->groupCreateChecks($share); + + //Verify the expiration date + $share = $this->validateExpirationDateInternal($share); + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + $this->linkCreateChecks($share); + $this->setLinkParent($share); + + /* + * For now ignore a set token. + */ + $share->setToken( + $this->secureRandom->generate( + \OC\Share\Constants::TOKEN_LENGTH, + \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE + ) + ); + + //Verify the expiration date + $share = $this->validateExpirationDate($share); + + //Verify the password + $this->verifyPassword($share->getPassword()); + + // If a password is set. Hash it! + if ($share->getPassword() !== null) { + $share->setPassword($this->hasher->hash($share->getPassword())); + } + } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { + $share->setToken( + $this->secureRandom->generate( + \OC\Share\Constants::TOKEN_LENGTH, + \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE + ) + ); + } + + // Cannot share with the owner + if ($share->getShareType() === IShare::TYPE_USER && + $share->getSharedWith() === $share->getShareOwner()) { + throw new \InvalidArgumentException('Can’t share with the share owner'); + } + + // Generate the target + $target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName(); + $target = \OC\Files\Filesystem::normalizePath($target); + $share->setTarget($target); + + // Pre share event + $event = new GenericEvent($share); + $this->legacyDispatcher->dispatch('OCP\Share::preShare', $event); + if ($event->isPropagationStopped() && $event->hasArgument('error')) { + throw new \Exception($event->getArgument('error')); + } + + $oldShare = $share; + $provider = $this->factory->getProviderForType($share->getShareType()); + $share = $provider->create($share); + //reuse the node we already have + $share->setNode($oldShare->getNode()); + + // Reset the target if it is null for the new share + if ($share->getTarget() === '') { + $share->setTarget($target); + } + + // Post share event + $event = new GenericEvent($share); + $this->legacyDispatcher->dispatch('OCP\Share::postShare', $event); + + $this->dispatcher->dispatchTyped(new Share\Events\ShareCreatedEvent($share)); + + if ($this->config->getSystemValueBool('sharing.enable_share_mail', true) + && $share->getShareType() === IShare::TYPE_USER) { + $mailSend = $share->getMailSend(); + if ($mailSend === true) { + $user = $this->userManager->get($share->getSharedWith()); + if ($user !== null) { + $emailAddress = $user->getEMailAddress(); + if ($emailAddress !== null && $emailAddress !== '') { + $userLang = $this->l10nFactory->getUserLanguage($user); + $l = $this->l10nFactory->get('lib', $userLang); + $this->sendMailNotification( + $l, + $share->getNode()->getName(), + $this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]), + $share->getSharedBy(), + $emailAddress, + $share->getExpirationDate() + ); + $this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']); + } else { + $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']); + } + } else { + $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']); + } + } else { + $this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']); + } + } + + return $share; + } + + /** + * Send mail notifications + * + * This method will catch and log mail transmission errors + * + * @param IL10N $l Language of the recipient + * @param string $filename file/folder name + * @param string $link link to the file/folder + * @param string $initiator user ID of share sender + * @param string $shareWith email address of share receiver + * @param \DateTime|null $expiration + */ + protected function sendMailNotification(IL10N $l, + $filename, + $link, + $initiator, + $shareWith, + \DateTime $expiration = null) { + $initiatorUser = $this->userManager->get($initiator); + $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; + + $message = $this->mailer->createMessage(); + + $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [ + 'filename' => $filename, + 'link' => $link, + 'initiator' => $initiatorDisplayName, + 'expiration' => $expiration, + 'shareWith' => $shareWith, + ]); + + $emailTemplate->setSubject($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename])); + $emailTemplate->addHeader(); + $emailTemplate->addHeading($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]), false); + $text = $l->t('%1$s shared »%2$s« with you.', [$initiatorDisplayName, $filename]); + + $emailTemplate->addBodyText( + htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')), + $text + ); + $emailTemplate->addBodyButton( + $l->t('Open »%s«', [$filename]), + $link + ); + + $message->setTo([$shareWith]); + + // The "From" contains the sharers name + $instanceName = $this->defaults->getName(); + $senderName = $l->t( + '%1$s via %2$s', + [ + $initiatorDisplayName, + $instanceName + ] + ); + $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); + + // The "Reply-To" is set to the sharer if an mail address is configured + // also the default footer contains a "Do not reply" which needs to be adjusted. + $initiatorEmail = $initiatorUser->getEMailAddress(); + if ($initiatorEmail !== null) { + $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]); + $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan($l->getLanguageCode()) !== '' ? ' - ' . $this->defaults->getSlogan($l->getLanguageCode()) : '')); + } else { + $emailTemplate->addFooter('', $l->getLanguageCode()); + } + + $message->useTemplate($emailTemplate); + try { + $failedRecipients = $this->mailer->send($message); + if (!empty($failedRecipients)) { + $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients)); + return; + } + } catch (\Exception $e) { + $this->logger->logException($e, ['message' => 'Share notification mail could not be sent']); + } + } + + /** + * Update a share + * + * @param IShare $share + * @return IShare The share object + * @throws \InvalidArgumentException + */ + public function updateShare(IShare $share) { + $expirationDateUpdated = false; + + $this->canShare($share); + + try { + $originalShare = $this->getShareById($share->getFullId()); + } catch (\UnexpectedValueException $e) { + throw new \InvalidArgumentException('Share does not have a full id'); + } + + // We can't change the share type! + if ($share->getShareType() !== $originalShare->getShareType()) { + throw new \InvalidArgumentException('Can’t change share type'); + } + + // We can only change the recipient on user shares + if ($share->getSharedWith() !== $originalShare->getSharedWith() && + $share->getShareType() !== IShare::TYPE_USER) { + throw new \InvalidArgumentException('Can only update recipient on user shares'); + } + + // Cannot share with the owner + if ($share->getShareType() === IShare::TYPE_USER && + $share->getSharedWith() === $share->getShareOwner()) { + throw new \InvalidArgumentException('Can’t share with the share owner'); + } + + $this->generalCreateChecks($share); + + if ($share->getShareType() === IShare::TYPE_USER) { + $this->userCreateChecks($share); + + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { + //Verify the expiration date + $this->validateExpirationDate($share); + $expirationDateUpdated = true; + } + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $this->groupCreateChecks($share); + + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { + //Verify the expiration date + $this->validateExpirationDate($share); + $expirationDateUpdated = true; + } + } elseif ($share->getShareType() === IShare::TYPE_LINK) { + $this->linkCreateChecks($share); + + $plainTextPassword = $share->getPassword(); + + $this->updateSharePasswordIfNeeded($share, $originalShare); + + if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) { + throw new \InvalidArgumentException('Can’t enable sending the password by Talk with an empty password'); + } + + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { + //Verify the expiration date + $this->validateExpirationDate($share); + $expirationDateUpdated = true; + } + } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { + // The new password is not set again if it is the same as the old + // one. + $plainTextPassword = $share->getPassword(); + if (!empty($plainTextPassword) && !$this->updateSharePasswordIfNeeded($share, $originalShare)) { + $plainTextPassword = null; + } + if (empty($plainTextPassword) && !$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) { + // If the same password was already sent by mail the recipient + // would already have access to the share without having to call + // the sharer to verify her identity + throw new \InvalidArgumentException('Can’t enable sending the password by Talk without setting a new password'); + } elseif (empty($plainTextPassword) && $originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) { + throw new \InvalidArgumentException('Can’t disable sending the password by Talk without setting a new password'); + } + } + + $this->pathCreateChecks($share->getNode()); + + // Now update the share! + $provider = $this->factory->getProviderForType($share->getShareType()); + if ($share->getShareType() === IShare::TYPE_EMAIL) { + $share = $provider->update($share, $plainTextPassword); + } else { + $share = $provider->update($share); + } + + if ($expirationDateUpdated === true) { + \OC_Hook::emit(Share::class, 'post_set_expiration_date', [ + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'date' => $share->getExpirationDate(), + 'uidOwner' => $share->getSharedBy(), + ]); + } + + if ($share->getPassword() !== $originalShare->getPassword()) { + \OC_Hook::emit(Share::class, 'post_update_password', [ + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'uidOwner' => $share->getSharedBy(), + 'token' => $share->getToken(), + 'disabled' => is_null($share->getPassword()), + ]); + } + + if ($share->getPermissions() !== $originalShare->getPermissions()) { + if ($this->userManager->userExists($share->getShareOwner())) { + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + } else { + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); + } + \OC_Hook::emit(Share::class, 'post_update_permissions', [ + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $share->getSharedWith(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'path' => $userFolder->getRelativePath($share->getNode()->getPath()), + ]); + } + + return $share; + } + + /** + * Accept a share. + * + * @param IShare $share + * @param string $recipientId + * @return IShare The share object + * @throws \InvalidArgumentException + * @since 9.0.0 + */ + public function acceptShare(IShare $share, string $recipientId): IShare { + [$providerId, ] = $this->splitFullId($share->getFullId()); + $provider = $this->factory->getProvider($providerId); + + if (!method_exists($provider, 'acceptShare')) { + // TODO FIX ME + throw new \InvalidArgumentException('Share provider does not support accepting'); + } + $provider->acceptShare($share, $recipientId); + $event = new GenericEvent($share); + $this->legacyDispatcher->dispatch('OCP\Share::postAcceptShare', $event); + + return $share; + } + + /** + * Updates the password of the given share if it is not the same as the + * password of the original share. + * + * @param IShare $share the share to update its password. + * @param IShare $originalShare the original share to compare its + * password with. + * @return boolean whether the password was updated or not. + */ + private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) { + $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) && + (($share->getPassword() !== null && $originalShare->getPassword() === null) || + ($share->getPassword() === null && $originalShare->getPassword() !== null) || + ($share->getPassword() !== null && $originalShare->getPassword() !== null && + !$this->hasher->verify($share->getPassword(), $originalShare->getPassword()))); + + // Password updated. + if ($passwordsAreDifferent) { + //Verify the password + $this->verifyPassword($share->getPassword()); + + // If a password is set. Hash it! + if ($share->getPassword() !== null) { + $share->setPassword($this->hasher->hash($share->getPassword())); + + return true; + } + } else { + // Reset the password to the original one, as it is either the same + // as the "new" password or a hashed version of it. + $share->setPassword($originalShare->getPassword()); + } + + return false; + } + + /** + * Delete all the children of this share + * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in + * + * @param IShare $share + * @return IShare[] List of deleted shares + */ + protected function deleteChildren(IShare $share) { + $deletedShares = []; + + $provider = $this->factory->getProviderForType($share->getShareType()); + + foreach ($provider->getChildren($share) as $child) { + $deletedChildren = $this->deleteChildren($child); + $deletedShares = array_merge($deletedShares, $deletedChildren); + + $provider->delete($child); + $deletedShares[] = $child; + } + + return $deletedShares; + } + + /** + * Delete a share + * + * @param IShare $share + * @throws ShareNotFound + * @throws \InvalidArgumentException + */ + public function deleteShare(IShare $share) { + try { + $share->getFullId(); + } catch (\UnexpectedValueException $e) { + throw new \InvalidArgumentException('Share does not have a full id'); + } + + $event = new GenericEvent($share); + $this->legacyDispatcher->dispatch('OCP\Share::preUnshare', $event); + + // Get all children and delete them as well + $deletedShares = $this->deleteChildren($share); + + // Do the actual delete + $provider = $this->factory->getProviderForType($share->getShareType()); + $provider->delete($share); + + // All the deleted shares caused by this delete + $deletedShares[] = $share; + + // Emit post hook + $event->setArgument('deletedShares', $deletedShares); + $this->legacyDispatcher->dispatch('OCP\Share::postUnshare', $event); + } + + + /** + * Unshare a file as the recipient. + * This can be different from a regular delete for example when one of + * the users in a groups deletes that share. But the provider should + * handle this. + * + * @param IShare $share + * @param string $recipientId + */ + public function deleteFromSelf(IShare $share, $recipientId) { + list($providerId, ) = $this->splitFullId($share->getFullId()); + $provider = $this->factory->getProvider($providerId); + + $provider->deleteFromSelf($share, $recipientId); + $event = new GenericEvent($share); + $this->legacyDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event); + } + + public function restoreShare(IShare $share, string $recipientId): IShare { + list($providerId, ) = $this->splitFullId($share->getFullId()); + $provider = $this->factory->getProvider($providerId); + + return $provider->restore($share, $recipientId); + } + + /** + * @inheritdoc + */ + public function moveShare(IShare $share, $recipientId) { + if ($share->getShareType() === IShare::TYPE_LINK) { + throw new \InvalidArgumentException('Can’t change target of link share'); + } + + if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) { + throw new \InvalidArgumentException('Invalid recipient'); + } + + if ($share->getShareType() === IShare::TYPE_GROUP) { + $sharedWith = $this->groupManager->get($share->getSharedWith()); + if (is_null($sharedWith)) { + throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist'); + } + $recipient = $this->userManager->get($recipientId); + if (!$sharedWith->inGroup($recipient)) { + throw new \InvalidArgumentException('Invalid recipient'); + } + } + + list($providerId, ) = $this->splitFullId($share->getFullId()); + $provider = $this->factory->getProvider($providerId); + + return $provider->move($share, $recipientId); + } + + public function getSharesInFolder($userId, Folder $node, $reshares = false) { + $providers = $this->factory->getAllProviders(); + + return array_reduce($providers, function ($shares, IShareProvider $provider) use ($userId, $node, $reshares) { + $newShares = $provider->getSharesInFolder($userId, $node, $reshares); + foreach ($newShares as $fid => $data) { + if (!isset($shares[$fid])) { + $shares[$fid] = []; + } + + $shares[$fid] = array_merge($shares[$fid], $data); + } + return $shares; + }, []); + } + + /** + * @inheritdoc + */ + public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) { + if ($path !== null && + !($path instanceof \OCP\Files\File) && + !($path instanceof \OCP\Files\Folder)) { + throw new \InvalidArgumentException('invalid path'); + } + + try { + $provider = $this->factory->getProviderForType($shareType); + } catch (ProviderException $e) { + return []; + } + + $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset); + + /* + * Work around so we don't return expired shares but still follow + * proper pagination. + */ + + $shares2 = []; + + while (true) { + $added = 0; + foreach ($shares as $share) { + try { + $this->checkExpireDate($share); + } catch (ShareNotFound $e) { + //Ignore since this basically means the share is deleted + continue; + } + + $added++; + $shares2[] = $share; + + if (count($shares2) === $limit) { + break; + } + } + + // If we did not fetch more shares than the limit then there are no more shares + if (count($shares) < $limit) { + break; + } + + if (count($shares2) === $limit) { + break; + } + + // If there was no limit on the select we are done + if ($limit === -1) { + break; + } + + $offset += $added; + + // Fetch again $limit shares + $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset); + + // No more shares means we are done + if (empty($shares)) { + break; + } + } + + $shares = $shares2; + + return $shares; + } + + /** + * @inheritdoc + */ + public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) { + try { + $provider = $this->factory->getProviderForType($shareType); + } catch (ProviderException $e) { + return []; + } + + $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset); + + // remove all shares which are already expired + foreach ($shares as $key => $share) { + try { + $this->checkExpireDate($share); + } catch (ShareNotFound $e) { + unset($shares[$key]); + } + } + + return $shares; + } + + /** + * @inheritdoc + */ + public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) { + $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset); + + // Only get deleted shares + $shares = array_filter($shares, function (IShare $share) { + return $share->getPermissions() === 0; + }); + + // Only get shares where the owner still exists + $shares = array_filter($shares, function (IShare $share) { + return $this->userManager->userExists($share->getShareOwner()); + }); + + return $shares; + } + + /** + * @inheritdoc + */ + public function getShareById($id, $recipient = null) { + if ($id === null) { + throw new ShareNotFound(); + } + + list($providerId, $id) = $this->splitFullId($id); + + try { + $provider = $this->factory->getProvider($providerId); + } catch (ProviderException $e) { + throw new ShareNotFound(); + } + + $share = $provider->getShareById($id, $recipient); + + $this->checkExpireDate($share); + + return $share; + } + + /** + * Get all the shares for a given path + * + * @param \OCP\Files\Node $path + * @param int $page + * @param int $perPage + * + * @return Share[] + */ + public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) { + return []; + } + + /** + * Get the share by token possible with password + * + * @param string $token + * @return IShare + * + * @throws ShareNotFound + */ + public function getShareByToken($token) { + // tokens can't be valid local user names + if ($this->userManager->userExists($token)) { + throw new ShareNotFound(); + } + $share = null; + try { + if ($this->shareApiAllowLinks()) { + $provider = $this->factory->getProviderForType(IShare::TYPE_LINK); + $share = $provider->getShareByToken($token); + } + } catch (ProviderException $e) { + } catch (ShareNotFound $e) { + } + + + // If it is not a link share try to fetch a federated share by token + if ($share === null) { + try { + $provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE); + $share = $provider->getShareByToken($token); + } catch (ProviderException $e) { + } catch (ShareNotFound $e) { + } + } + + // If it is not a link share try to fetch a mail share by token + if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) { + try { + $provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL); + $share = $provider->getShareByToken($token); + } catch (ProviderException $e) { + } catch (ShareNotFound $e) { + } + } + + if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) { + try { + $provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE); + $share = $provider->getShareByToken($token); + } catch (ProviderException $e) { + } catch (ShareNotFound $e) { + } + } + + if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) { + try { + $provider = $this->factory->getProviderForType(IShare::TYPE_ROOM); + $share = $provider->getShareByToken($token); + } catch (ProviderException $e) { + } catch (ShareNotFound $e) { + } + } + + if ($share === null) { + throw new ShareNotFound($this->l->t('The requested share does not exist anymore')); + } + + $this->checkExpireDate($share); + + /* + * Reduce the permissions for link shares if public upload is not enabled + */ + if ($share->getShareType() === IShare::TYPE_LINK && + !$this->shareApiLinkAllowPublicUpload()) { + $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)); + } + + return $share; + } + + protected function checkExpireDate($share) { + if ($share->isExpired()) { + $this->deleteShare($share); + throw new ShareNotFound($this->l->t('The requested share does not exist anymore')); + } + } + + /** + * Verify the password of a public share + * + * @param IShare $share + * @param string $password + * @return bool + */ + public function checkPassword(IShare $share, $password) { + $passwordProtected = $share->getShareType() !== IShare::TYPE_LINK + || $share->getShareType() !== IShare::TYPE_EMAIL + || $share->getShareType() !== IShare::TYPE_CIRCLE; + if (!$passwordProtected) { + //TODO maybe exception? + return false; + } + + if ($password === null || $share->getPassword() === null) { + return false; + } + + $newHash = ''; + if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) { + return false; + } + + if (!empty($newHash)) { + $share->setPassword($newHash); + $provider = $this->factory->getProviderForType($share->getShareType()); + $provider->update($share); + } + + return true; + } + + /** + * @inheritdoc + */ + public function userDeleted($uid) { + $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL]; + + foreach ($types as $type) { + try { + $provider = $this->factory->getProviderForType($type); + } catch (ProviderException $e) { + continue; + } + $provider->userDeleted($uid, $type); + } + } + + /** + * @inheritdoc + */ + public function groupDeleted($gid) { + $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP); + $provider->groupDeleted($gid); + + $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + if ($excludedGroups === '') { + return; + } + + $excludedGroups = json_decode($excludedGroups, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return; + } + + $excludedGroups = array_diff($excludedGroups, [$gid]); + $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups)); + } + + /** + * @inheritdoc + */ + public function userDeletedFromGroup($uid, $gid) { + $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP); + $provider->userDeletedFromGroup($uid, $gid); + } + + /** + * Get access list to a path. This means + * all the users that can access a given path. + * + * Consider: + * -root + * |-folder1 (23) + * |-folder2 (32) + * |-fileA (42) + * + * fileA is shared with user1 and user1@server1 + * folder2 is shared with group2 (user4 is a member of group2) + * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2 + * + * Then the access list to '/folder1/folder2/fileA' with $currentAccess is: + * [ + * users => [ + * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'], + * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'], + * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'], + * ], + * remote => [ + * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'], + * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'], + * ], + * public => bool + * mail => bool + * ] + * + * The access list to '/folder1/folder2/fileA' **without** $currentAccess is: + * [ + * users => ['user1', 'user2', 'user4'], + * remote => bool, + * public => bool + * mail => bool + * ] + * + * This is required for encryption/activity + * + * @param \OCP\Files\Node $path + * @param bool $recursive Should we check all parent folders as well + * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it) + * @return array + */ + public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) { + $owner = $path->getOwner(); + + if ($owner === null) { + return []; + } + + $owner = $owner->getUID(); + + if ($currentAccess) { + $al = ['users' => [], 'remote' => [], 'public' => false]; + } else { + $al = ['users' => [], 'remote' => false, 'public' => false]; + } + if (!$this->userManager->userExists($owner)) { + return $al; + } + + //Get node for the owner and correct the owner in case of external storages + $userFolder = $this->rootFolder->getUserFolder($owner); + if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) { + $nodes = $userFolder->getById($path->getId()); + $path = array_shift($nodes); + if ($path->getOwner() === null) { + return []; + } + $owner = $path->getOwner()->getUID(); + } + + $providers = $this->factory->getAllProviders(); + + /** @var Node[] $nodes */ + $nodes = []; + + + if ($currentAccess) { + $ownerPath = $path->getPath(); + $ownerPath = explode('/', $ownerPath, 4); + if (count($ownerPath) < 4) { + $ownerPath = ''; + } else { + $ownerPath = $ownerPath[3]; + } + $al['users'][$owner] = [ + 'node_id' => $path->getId(), + 'node_path' => '/' . $ownerPath, + ]; + } else { + $al['users'][] = $owner; + } + + // Collect all the shares + while ($path->getPath() !== $userFolder->getPath()) { + $nodes[] = $path; + if (!$recursive) { + break; + } + $path = $path->getParent(); + } + + foreach ($providers as $provider) { + $tmp = $provider->getAccessList($nodes, $currentAccess); + + foreach ($tmp as $k => $v) { + if (isset($al[$k])) { + if (is_array($al[$k])) { + if ($currentAccess) { + $al[$k] += $v; + } else { + $al[$k] = array_merge($al[$k], $v); + $al[$k] = array_unique($al[$k]); + $al[$k] = array_values($al[$k]); + } + } else { + $al[$k] = $al[$k] || $v; + } + } else { + $al[$k] = $v; + } + } + } + + return $al; + } + + /** + * Create a new share + * + * @return IShare + */ + public function newShare() { + return new \OC\Share20\Share($this->rootFolder, $this->userManager); + } + + /** + * Is the share API enabled + * + * @return bool + */ + public function shareApiEnabled() { + return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes'; + } + + /** + * Is public link sharing enabled + * + * @return bool + */ + public function shareApiAllowLinks() { + return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes'; + } + + /** + * Is password on public link requires + * + * @return bool + */ + public function shareApiLinkEnforcePassword() { + return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes'; + } + + /** + * Is default link expire date enabled + * + * @return bool + */ + public function shareApiLinkDefaultExpireDate() { + return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + } + + /** + * Is default link expire date enforced + *` + * @return bool + */ + public function shareApiLinkDefaultExpireDateEnforced() { + return $this->shareApiLinkDefaultExpireDate() && + $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + + + /** + * Number of default link expire days + * @return int + */ + public function shareApiLinkDefaultExpireDays() { + return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + } + + /** + * Is default internal expire date enabled + * + * @return bool + */ + public function shareApiInternalDefaultExpireDate(): bool { + return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes'; + } + + /** + * Is default expire date enforced + *` + * @return bool + */ + public function shareApiInternalDefaultExpireDateEnforced(): bool { + return $this->shareApiInternalDefaultExpireDate() && + $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes'; + } + + + /** + * Number of default expire days + * @return int + */ + public function shareApiInternalDefaultExpireDays(): int { + return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'); + } + + /** + * Allow public upload on link shares + * + * @return bool + */ + public function shareApiLinkAllowPublicUpload() { + return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes'; + } + + /** + * check if user can only share with group members + * @return bool + */ + public function shareWithGroupMembersOnly() { + return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + } + + /** + * Check if users can share with groups + * @return bool + */ + public function allowGroupSharing() { + return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes'; + } + + public function allowEnumeration(): bool { + return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function limitEnumerationToGroups(): bool { + return $this->allowEnumeration() && + $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + } + + /** + * Copied from \OC_Util::isSharingDisabledForUser + * + * TODO: Deprecate fuction from OC_Util + * + * @param string $userId + * @return bool + */ + public function sharingDisabledForUser($userId) { + if ($userId === null) { + return false; + } + + if (isset($this->sharingDisabledForUsersCache[$userId])) { + return $this->sharingDisabledForUsersCache[$userId]; + } + + if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { + $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $excludedGroups = json_decode($groupsList); + if (is_null($excludedGroups)) { + $excludedGroups = explode(',', $groupsList); + $newValue = json_encode($excludedGroups); + $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); + } + $user = $this->userManager->get($userId); + $usersGroups = $this->groupManager->getUserGroupIds($user); + if (!empty($usersGroups)) { + $remainingGroups = array_diff($usersGroups, $excludedGroups); + // if the user is only in groups which are disabled for sharing then + // sharing is also disabled for the user + if (empty($remainingGroups)) { + $this->sharingDisabledForUsersCache[$userId] = true; + return true; + } + } + } + + $this->sharingDisabledForUsersCache[$userId] = false; + return false; + } + + /** + * @inheritdoc + */ + public function outgoingServer2ServerSharesAllowed() { + return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; + } + + /** + * @inheritdoc + */ + public function outgoingServer2ServerGroupSharesAllowed() { + return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes'; + } + + /** + * @inheritdoc + */ + public function shareProviderExists($shareType) { + try { + $this->factory->getProviderForType($shareType); + } catch (ProviderException $e) { + return false; + } + + return true; + } + + public function getAllShares(): iterable { + $providers = $this->factory->getAllProviders(); + + foreach ($providers as $provider) { + yield from $provider->getAllShares(); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/ProviderFactory.php b/docker/overlays/nextcloud/html/lib/private/Share20/ProviderFactory.php new file mode 100644 index 0000000..2d4c4e6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/ProviderFactory.php @@ -0,0 +1,325 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Maxence Lange + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20; + +use OC\Share20\Exception\ProviderException; +use OCA\FederatedFileSharing\AddressHandler; +use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\FederatedFileSharing\Notifications; +use OCA\FederatedFileSharing\TokenHandler; +use OCA\ShareByMail\Settings\SettingsManager; +use OCA\ShareByMail\ShareByMailProvider; +use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IServerContainer; +use OCP\Share\IProviderFactory; +use OCP\Share\IShare; + +/** + * Class ProviderFactory + * + * @package OC\Share20 + */ +class ProviderFactory implements IProviderFactory { + + /** @var IServerContainer */ + private $serverContainer; + /** @var DefaultShareProvider */ + private $defaultProvider = null; + /** @var FederatedShareProvider */ + private $federatedProvider = null; + /** @var ShareByMailProvider */ + private $shareByMailProvider; + /** @var \OCA\Circles\ShareByCircleProvider */ + private $shareByCircleProvider = null; + /** @var bool */ + private $circlesAreNotAvailable = false; + /** @var \OCA\Talk\Share\RoomShareProvider */ + private $roomShareProvider = null; + + /** + * IProviderFactory constructor. + * + * @param IServerContainer $serverContainer + */ + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + /** + * Create the default share provider. + * + * @return DefaultShareProvider + */ + protected function defaultShareProvider() { + if ($this->defaultProvider === null) { + $this->defaultProvider = new DefaultShareProvider( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getUserManager(), + $this->serverContainer->getGroupManager(), + $this->serverContainer->getLazyRootFolder(), + $this->serverContainer->getMailer(), + $this->serverContainer->query(Defaults::class), + $this->serverContainer->getL10NFactory(), + $this->serverContainer->getURLGenerator(), + $this->serverContainer->getConfig() + ); + } + + return $this->defaultProvider; + } + + /** + * Create the federated share provider + * + * @return FederatedShareProvider + */ + protected function federatedShareProvider() { + if ($this->federatedProvider === null) { + /* + * Check if the app is enabled + */ + $appManager = $this->serverContainer->getAppManager(); + if (!$appManager->isEnabledForUser('federatedfilesharing')) { + return null; + } + + /* + * TODO: add factory to federated sharing app + */ + $l = $this->serverContainer->getL10N('federatedfilesharing'); + $addressHandler = new AddressHandler( + $this->serverContainer->getURLGenerator(), + $l, + $this->serverContainer->getCloudIdManager() + ); + $notifications = new Notifications( + $addressHandler, + $this->serverContainer->getHTTPClientService(), + $this->serverContainer->query(\OCP\OCS\IDiscoveryService::class), + $this->serverContainer->getJobList(), + \OC::$server->getCloudFederationProviderManager(), + \OC::$server->getCloudFederationFactory(), + $this->serverContainer->query(IEventDispatcher::class) + ); + $tokenHandler = new TokenHandler( + $this->serverContainer->getSecureRandom() + ); + + $this->federatedProvider = new FederatedShareProvider( + $this->serverContainer->getDatabaseConnection(), + $addressHandler, + $notifications, + $tokenHandler, + $l, + $this->serverContainer->getLogger(), + $this->serverContainer->getLazyRootFolder(), + $this->serverContainer->getConfig(), + $this->serverContainer->getUserManager(), + $this->serverContainer->getCloudIdManager(), + $this->serverContainer->getGlobalScaleConfig(), + $this->serverContainer->getCloudFederationProviderManager() + ); + } + + return $this->federatedProvider; + } + + /** + * Create the federated share provider + * + * @return ShareByMailProvider + */ + protected function getShareByMailProvider() { + if ($this->shareByMailProvider === null) { + /* + * Check if the app is enabled + */ + $appManager = $this->serverContainer->getAppManager(); + if (!$appManager->isEnabledForUser('sharebymail')) { + return null; + } + + $settingsManager = new SettingsManager($this->serverContainer->getConfig()); + + $this->shareByMailProvider = new ShareByMailProvider( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getSecureRandom(), + $this->serverContainer->getUserManager(), + $this->serverContainer->getLazyRootFolder(), + $this->serverContainer->getL10N('sharebymail'), + $this->serverContainer->getLogger(), + $this->serverContainer->getMailer(), + $this->serverContainer->getURLGenerator(), + $this->serverContainer->getActivityManager(), + $settingsManager, + $this->serverContainer->query(Defaults::class), + $this->serverContainer->getHasher(), + $this->serverContainer->get(IEventDispatcher::class) + ); + } + + return $this->shareByMailProvider; + } + + + /** + * Create the circle share provider + * + * @return FederatedShareProvider + * + * @suppress PhanUndeclaredClassMethod + */ + protected function getShareByCircleProvider() { + if ($this->circlesAreNotAvailable) { + return null; + } + + if (!$this->serverContainer->getAppManager()->isEnabledForUser('circles') || + !class_exists('\OCA\Circles\ShareByCircleProvider') + ) { + $this->circlesAreNotAvailable = true; + return null; + } + + if ($this->shareByCircleProvider === null) { + $this->shareByCircleProvider = new \OCA\Circles\ShareByCircleProvider( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getSecureRandom(), + $this->serverContainer->getUserManager(), + $this->serverContainer->getLazyRootFolder(), + $this->serverContainer->getL10N('circles'), + $this->serverContainer->getLogger(), + $this->serverContainer->getURLGenerator() + ); + } + + return $this->shareByCircleProvider; + } + + /** + * Create the room share provider + * + * @return RoomShareProvider + */ + protected function getRoomShareProvider() { + if ($this->roomShareProvider === null) { + /* + * Check if the app is enabled + */ + $appManager = $this->serverContainer->getAppManager(); + if (!$appManager->isEnabledForUser('spreed')) { + return null; + } + + try { + $this->roomShareProvider = $this->serverContainer->query('\OCA\Talk\Share\RoomShareProvider'); + } catch (\OCP\AppFramework\QueryException $e) { + return null; + } + } + + return $this->roomShareProvider; + } + + /** + * @inheritdoc + */ + public function getProvider($id) { + $provider = null; + if ($id === 'ocinternal') { + $provider = $this->defaultShareProvider(); + } elseif ($id === 'ocFederatedSharing') { + $provider = $this->federatedShareProvider(); + } elseif ($id === 'ocMailShare') { + $provider = $this->getShareByMailProvider(); + } elseif ($id === 'ocCircleShare') { + $provider = $this->getShareByCircleProvider(); + } elseif ($id === 'ocRoomShare') { + $provider = $this->getRoomShareProvider(); + } + + if ($provider === null) { + throw new ProviderException('No provider with id .' . $id . ' found.'); + } + + return $provider; + } + + /** + * @inheritdoc + */ + public function getProviderForType($shareType) { + $provider = null; + + if ($shareType === IShare::TYPE_USER || + $shareType === IShare::TYPE_GROUP || + $shareType === IShare::TYPE_LINK + ) { + $provider = $this->defaultShareProvider(); + } elseif ($shareType === IShare::TYPE_REMOTE || $shareType === IShare::TYPE_REMOTE_GROUP) { + $provider = $this->federatedShareProvider(); + } elseif ($shareType === IShare::TYPE_EMAIL) { + $provider = $this->getShareByMailProvider(); + } elseif ($shareType === IShare::TYPE_CIRCLE) { + $provider = $this->getShareByCircleProvider(); + } elseif ($shareType === IShare::TYPE_ROOM) { + $provider = $this->getRoomShareProvider(); + } + + + if ($provider === null) { + throw new ProviderException('No share provider for share type ' . $shareType); + } + + return $provider; + } + + public function getAllProviders() { + $shares = [$this->defaultShareProvider(), $this->federatedShareProvider()]; + $shareByMail = $this->getShareByMailProvider(); + if ($shareByMail !== null) { + $shares[] = $shareByMail; + } + $shareByCircle = $this->getShareByCircleProvider(); + if ($shareByCircle !== null) { + $shares[] = $shareByCircle; + } + $roomShare = $this->getRoomShareProvider(); + if ($roomShare !== null) { + $shares[] = $roomShare; + } + + return $shares; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/Share.php b/docker/overlays/nextcloud/html/lib/private/Share20/Share.php new file mode 100644 index 0000000..69f36ed --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/Share.php @@ -0,0 +1,578 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Maxence Lange + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Share20; + +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\IUserManager; +use OCP\Share\Exceptions\IllegalIDChangeException; +use OCP\Share\IShare; + +class Share implements \OCP\Share\IShare { + + /** @var string */ + private $id; + /** @var string */ + private $providerId; + /** @var Node */ + private $node; + /** @var int */ + private $fileId; + /** @var string */ + private $nodeType; + /** @var int */ + private $shareType; + /** @var string */ + private $sharedWith; + /** @var string */ + private $sharedWithDisplayName; + /** @var string */ + private $sharedWithAvatar; + /** @var string */ + private $sharedBy; + /** @var string */ + private $shareOwner; + /** @var int */ + private $permissions; + /** @var int */ + private $status; + /** @var string */ + private $note = ''; + /** @var \DateTime */ + private $expireDate; + /** @var string */ + private $password; + /** @var bool */ + private $sendPasswordByTalk = false; + /** @var string */ + private $token; + /** @var int */ + private $parent; + /** @var string */ + private $target; + /** @var \DateTime */ + private $shareTime; + /** @var bool */ + private $mailSend; + /** @var string */ + private $label = ''; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var IUserManager */ + private $userManager; + + /** @var ICacheEntry|null */ + private $nodeCacheEntry; + + /** @var bool */ + private $hideDownload = false; + + public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { + $this->rootFolder = $rootFolder; + $this->userManager = $userManager; + } + + /** + * @inheritdoc + */ + public function setId($id) { + if (is_int($id)) { + $id = (string)$id; + } + + if (!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + + if ($this->id !== null) { + throw new IllegalIDChangeException('Not allowed to assign a new internal id to a share'); + } + + $this->id = trim($id); + return $this; + } + + /** + * @inheritdoc + */ + public function getId() { + return $this->id; + } + + /** + * @inheritdoc + */ + public function getFullId() { + if ($this->providerId === null || $this->id === null) { + throw new \UnexpectedValueException; + } + return $this->providerId . ':' . $this->id; + } + + /** + * @inheritdoc + */ + public function setProviderId($id) { + if (!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + + if ($this->providerId !== null) { + throw new IllegalIDChangeException('Not allowed to assign a new provider id to a share'); + } + + $this->providerId = trim($id); + return $this; + } + + /** + * @inheritdoc + */ + public function setNode(Node $node) { + $this->fileId = null; + $this->nodeType = null; + $this->node = $node; + return $this; + } + + /** + * @inheritdoc + */ + public function getNode() { + if ($this->node === null) { + if ($this->shareOwner === null || $this->fileId === null) { + throw new NotFoundException(); + } + + // for federated shares the owner can be a remote user, in this + // case we use the initiator + if ($this->userManager->userExists($this->shareOwner)) { + $userFolder = $this->rootFolder->getUserFolder($this->shareOwner); + } else { + $userFolder = $this->rootFolder->getUserFolder($this->sharedBy); + } + + $nodes = $userFolder->getById($this->fileId); + if (empty($nodes)) { + throw new NotFoundException('Node for share not found, fileid: ' . $this->fileId); + } + + $this->node = $nodes[0]; + } + + return $this->node; + } + + /** + * @inheritdoc + */ + public function setNodeId($fileId) { + $this->node = null; + $this->fileId = $fileId; + return $this; + } + + /** + * @inheritdoc + */ + public function getNodeId() { + if ($this->fileId === null) { + $this->fileId = $this->getNode()->getId(); + } + + return $this->fileId; + } + + /** + * @inheritdoc + */ + public function setNodeType($type) { + if ($type !== 'file' && $type !== 'folder') { + throw new \InvalidArgumentException(); + } + + $this->nodeType = $type; + return $this; + } + + /** + * @inheritdoc + */ + public function getNodeType() { + if ($this->nodeType === null) { + $node = $this->getNode(); + $this->nodeType = $node instanceof File ? 'file' : 'folder'; + } + + return $this->nodeType; + } + + /** + * @inheritdoc + */ + public function setShareType($shareType) { + $this->shareType = $shareType; + return $this; + } + + /** + * @inheritdoc + */ + public function getShareType() { + return $this->shareType; + } + + /** + * @inheritdoc + */ + public function setSharedWith($sharedWith) { + if (!is_string($sharedWith)) { + throw new \InvalidArgumentException(); + } + $this->sharedWith = $sharedWith; + return $this; + } + + /** + * @inheritdoc + */ + public function getSharedWith() { + return $this->sharedWith; + } + + /** + * @inheritdoc + */ + public function setSharedWithDisplayName($displayName) { + if (!is_string($displayName)) { + throw new \InvalidArgumentException(); + } + $this->sharedWithDisplayName = $displayName; + return $this; + } + + /** + * @inheritdoc + */ + public function getSharedWithDisplayName() { + return $this->sharedWithDisplayName; + } + + /** + * @inheritdoc + */ + public function setSharedWithAvatar($src) { + if (!is_string($src)) { + throw new \InvalidArgumentException(); + } + $this->sharedWithAvatar = $src; + return $this; + } + + /** + * @inheritdoc + */ + public function getSharedWithAvatar() { + return $this->sharedWithAvatar; + } + + /** + * @inheritdoc + */ + public function setPermissions($permissions) { + //TODO checkes + + $this->permissions = $permissions; + return $this; + } + + /** + * @inheritdoc + */ + public function getPermissions() { + return $this->permissions; + } + + /** + * @inheritdoc + */ + public function setStatus(int $status): IShare { + $this->status = $status; + return $this; + } + + /** + * @inheritdoc + */ + public function getStatus(): int { + return $this->status; + } + + /** + * @inheritdoc + */ + public function setNote($note) { + $this->note = $note; + return $this; + } + + /** + * @inheritdoc + */ + public function getNote() { + if (is_string($this->note)) { + return $this->note; + } + return ''; + } + + /** + * @inheritdoc + */ + public function setLabel($label) { + $this->label = $label; + return $this; + } + + /** + * @inheritdoc + */ + public function getLabel() { + return $this->label; + } + + /** + * @inheritdoc + */ + public function setExpirationDate($expireDate) { + //TODO checks + + $this->expireDate = $expireDate; + return $this; + } + + /** + * @inheritdoc + */ + public function getExpirationDate() { + return $this->expireDate; + } + + /** + * @inheritdoc + */ + public function isExpired() { + return $this->getExpirationDate() !== null && + $this->getExpirationDate() <= new \DateTime(); + } + + /** + * @inheritdoc + */ + public function setSharedBy($sharedBy) { + if (!is_string($sharedBy)) { + throw new \InvalidArgumentException(); + } + //TODO checks + $this->sharedBy = $sharedBy; + + return $this; + } + + /** + * @inheritdoc + */ + public function getSharedBy() { + //TODO check if set + return $this->sharedBy; + } + + /** + * @inheritdoc + */ + public function setShareOwner($shareOwner) { + if (!is_string($shareOwner)) { + throw new \InvalidArgumentException(); + } + //TODO checks + + $this->shareOwner = $shareOwner; + return $this; + } + + /** + * @inheritdoc + */ + public function getShareOwner() { + //TODO check if set + return $this->shareOwner; + } + + /** + * @inheritdoc + */ + public function setPassword($password) { + $this->password = $password; + return $this; + } + + /** + * @inheritdoc + */ + public function getPassword() { + return $this->password; + } + + /** + * @inheritdoc + */ + public function setSendPasswordByTalk(bool $sendPasswordByTalk) { + $this->sendPasswordByTalk = $sendPasswordByTalk; + return $this; + } + + /** + * @inheritdoc + */ + public function getSendPasswordByTalk(): bool { + return $this->sendPasswordByTalk; + } + + /** + * @inheritdoc + */ + public function setToken($token) { + $this->token = $token; + return $this; + } + + /** + * @inheritdoc + */ + public function getToken() { + return $this->token; + } + + /** + * Set the parent of this share + * + * @param int parent + * @return \OCP\Share\IShare + * @deprecated The new shares do not have parents. This is just here for legacy reasons. + */ + public function setParent($parent) { + $this->parent = $parent; + return $this; + } + + /** + * Get the parent of this share. + * + * @return int + * @deprecated The new shares do not have parents. This is just here for legacy reasons. + */ + public function getParent() { + return $this->parent; + } + + /** + * @inheritdoc + */ + public function setTarget($target) { + $this->target = $target; + return $this; + } + + /** + * @inheritdoc + */ + public function getTarget() { + return $this->target; + } + + /** + * @inheritdoc + */ + public function setShareTime(\DateTime $shareTime) { + $this->shareTime = $shareTime; + return $this; + } + + /** + * @inheritdoc + */ + public function getShareTime() { + return $this->shareTime; + } + + /** + * @inheritdoc + */ + public function setMailSend($mailSend) { + $this->mailSend = $mailSend; + return $this; + } + + /** + * @inheritdoc + */ + public function getMailSend() { + return $this->mailSend; + } + + /** + * @inheritdoc + */ + public function setNodeCacheEntry(ICacheEntry $entry) { + $this->nodeCacheEntry = $entry; + } + + /** + * @inheritdoc + */ + public function getNodeCacheEntry() { + return $this->nodeCacheEntry; + } + + public function setHideDownload(bool $hide): IShare { + $this->hideDownload = $hide; + return $this; + } + + public function getHideDownload(): bool { + return $this->hideDownload; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/ShareHelper.php b/docker/overlays/nextcloud/html/lib/private/Share20/ShareHelper.php new file mode 100644 index 0000000..c7f1600 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/ShareHelper.php @@ -0,0 +1,219 @@ + + * + * @author Joas Schilling + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Share20; + +use OCP\Files\InvalidPathException; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Share\IManager; +use OCP\Share\IShareHelper; + +class ShareHelper implements IShareHelper { + + /** @var IManager */ + private $shareManager; + + public function __construct(IManager $shareManager) { + $this->shareManager = $shareManager; + } + + /** + * @param Node $node + * @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]] + */ + public function getPathsForAccessList(Node $node) { + $result = [ + 'users' => [], + 'remotes' => [], + ]; + + $accessList = $this->shareManager->getAccessList($node, true, true); + if (!empty($accessList['users'])) { + $result['users'] = $this->getPathsForUsers($node, $accessList['users']); + } + if (!empty($accessList['remote'])) { + $result['remotes'] = $this->getPathsForRemotes($node, $accessList['remote']); + } + + return $result; + } + + /** + * Sample: + * $users = [ + * 'test1' => ['node_id' => 16, 'node_path' => '/foo'], + * 'test2' => ['node_id' => 23, 'node_path' => '/bar'], + * 'test3' => ['node_id' => 42, 'node_path' => '/cat'], + * 'test4' => ['node_id' => 48, 'node_path' => '/dog'], + * ]; + * + * Node tree: + * - SixTeen is the parent of TwentyThree + * - TwentyThree is the parent of FortyTwo + * - FortyEight does not exist + * + * $return = [ + * 'test1' => '/foo/TwentyThree/FortyTwo', + * 'test2' => '/bar/FortyTwo', + * 'test3' => '/cat', + * ], + * + * @param Node $node + * @param array[] $users + * @return array + */ + protected function getPathsForUsers(Node $node, array $users) { + /** @var array[] $byId */ + $byId = []; + /** @var array[] $results */ + $results = []; + + foreach ($users as $uid => $info) { + if (!isset($byId[$info['node_id']])) { + $byId[$info['node_id']] = []; + } + $byId[$info['node_id']][$uid] = $info['node_path']; + } + + try { + if (isset($byId[$node->getId()])) { + foreach ($byId[$node->getId()] as $uid => $path) { + $results[$uid] = $path; + } + unset($byId[$node->getId()]); + } + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } + + if (empty($byId)) { + return $results; + } + + $item = $node; + $appendix = '/' . $node->getName(); + while (!empty($byId)) { + try { + /** @var Node $item */ + $item = $item->getParent(); + + if (!empty($byId[$item->getId()])) { + foreach ($byId[$item->getId()] as $uid => $path) { + $results[$uid] = $path . $appendix; + } + unset($byId[$item->getId()]); + } + + $appendix = '/' . $item->getName() . $appendix; + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } catch (NotPermittedException $e) { + return $results; + } + } + + return $results; + } + + /** + * Sample: + * $remotes = [ + * 'test1' => ['node_id' => 16, 'token' => 't1'], + * 'test2' => ['node_id' => 23, 'token' => 't2'], + * 'test3' => ['node_id' => 42, 'token' => 't3'], + * 'test4' => ['node_id' => 48, 'token' => 't4'], + * ]; + * + * Node tree: + * - SixTeen is the parent of TwentyThree + * - TwentyThree is the parent of FortyTwo + * - FortyEight does not exist + * + * $return = [ + * 'test1' => ['token' => 't1', 'node_path' => '/SixTeen'], + * 'test2' => ['token' => 't2', 'node_path' => '/SixTeen/TwentyThree'], + * 'test3' => ['token' => 't3', 'node_path' => '/SixTeen/TwentyThree/FortyTwo'], + * ], + * + * @param Node $node + * @param array[] $remotes + * @return array + */ + protected function getPathsForRemotes(Node $node, array $remotes) { + /** @var array[] $byId */ + $byId = []; + /** @var array[] $results */ + $results = []; + + foreach ($remotes as $cloudId => $info) { + if (!isset($byId[$info['node_id']])) { + $byId[$info['node_id']] = []; + } + $byId[$info['node_id']][$cloudId] = $info['token']; + } + + $item = $node; + while (!empty($byId)) { + try { + if (!empty($byId[$item->getId()])) { + $path = $this->getMountedPath($item); + foreach ($byId[$item->getId()] as $uid => $token) { + $results[$uid] = [ + 'node_path' => $path, + 'token' => $token, + ]; + } + unset($byId[$item->getId()]); + } + + /** @var Node $item */ + $item = $item->getParent(); + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } catch (NotPermittedException $e) { + return $results; + } + } + + return $results; + } + + /** + * @param Node $node + * @return string + */ + protected function getMountedPath(Node $node) { + $path = $node->getPath(); + $sections = explode('/', $path, 4); + return '/' . $sections[3]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Share20/UserRemovedListener.php b/docker/overlays/nextcloud/html/lib/private/Share20/UserRemovedListener.php new file mode 100644 index 0000000..0a81d04 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Share20/UserRemovedListener.php @@ -0,0 +1,50 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Share20; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Group\Events\UserRemovedEvent; +use OCP\Share\IManager; + +class UserRemovedListener implements IEventListener { + + /** @var IManager */ + protected $shareManager; + + public function __construct(IManager $shareManager) { + $this->shareManager = $shareManager; + } + + public function handle(Event $event): void { + if (!$event instanceof UserRemovedEvent) { + return; + } + + $this->shareManager->userDeletedFromGroup($event->getUser()->getUID(), $event->getGroup()->getGID()); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Streamer.php b/docker/overlays/nextcloud/html/lib/private/Streamer.php new file mode 100644 index 0000000..0e3018f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Streamer.php @@ -0,0 +1,190 @@ + + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\Files\Filesystem; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\InvalidPathException; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IRequest; +use ownCloud\TarStreamer\TarStreamer; +use ZipStreamer\ZipStreamer; + +class Streamer { + // array of regexp. Matching user agents will get tar instead of zip + private $preferTarFor = [ '/macintosh|mac os x/i' ]; + + // streamer instance + private $streamerInstance; + + /** + * Streamer constructor. + * + * @param IRequest $request + * @param int $size The size of the files in bytes + * @param int $numberOfFiles The number of files (and directories) that will + * be included in the streamed file + */ + public function __construct(IRequest $request, int $size, int $numberOfFiles) { + + /** + * zip32 constraints for a basic (without compression, volumes nor + * encryption) zip file according to the Zip specification: + * - No file size is larger than 4 bytes (file size < 4294967296); see + * 4.4.9 uncompressed size + * - The size of all files plus their local headers is not larger than + * 4 bytes; see 4.4.16 relative offset of local header and 4.4.24 + * offset of start of central directory with respect to the starting + * disk number + * - The total number of entries (files and directories) in the zip file + * is not larger than 2 bytes (number of entries < 65536); see 4.4.22 + * total number of entries in the central dir + * - The size of the central directory is not larger than 4 bytes; see + * 4.4.23 size of the central directory + * + * Due to all that, zip32 is used if the size is below 4GB and there are + * less than 65536 files; the margin between 4*1000^3 and 4*1024^3 + * should give enough room for the extra zip metadata. Technically, it + * would still be possible to create an invalid zip32 file (for example, + * a zip file from files smaller than 4GB with a central directory + * larger than 4GiB), but it should not happen in the real world. + * + * We also have to check for a size above 0. As negative sizes could be + * from not fully scanned external storages. And then things fall apart + * if somebody tries to package to much. + */ + if ($size > 0 && $size < 4 * 1000 * 1000 * 1000 && $numberOfFiles < 65536) { + $this->streamerInstance = new ZipStreamer(['zip64' => false]); + } elseif ($request->isUserAgent($this->preferTarFor)) { + $this->streamerInstance = new TarStreamer(); + } else { + $this->streamerInstance = new ZipStreamer(['zip64' => PHP_INT_SIZE !== 4]); + } + } + + /** + * Send HTTP headers + * @param string $name + */ + public function sendHeaders($name) { + $extension = $this->streamerInstance instanceof ZipStreamer ? '.zip' : '.tar'; + $fullName = $name . $extension; + $this->streamerInstance->sendHeaders($fullName); + } + + /** + * Stream directory recursively + * + * @throws NotFoundException + * @throws NotPermittedException + * @throws InvalidPathException + */ + public function addDirRecursive(string $dir, string $internalDir = ''): void { + $dirname = basename($dir); + $rootDir = $internalDir . $dirname; + if (!empty($rootDir)) { + $this->streamerInstance->addEmptyDir($rootDir); + } + $internalDir .= $dirname . '/'; + // prevent absolute dirs + $internalDir = ltrim($internalDir, '/'); + + $userFolder = \OC::$server->getRootFolder()->get(Filesystem::getRoot()); + /** @var Folder $dirNode */ + $dirNode = $userFolder->get($dir); + $files = $dirNode->getDirectoryListing(); + + foreach ($files as $file) { + if ($file instanceof File) { + try { + $fh = $file->fopen('r'); + } catch (NotPermittedException $e) { + continue; + } + $this->addFileFromStream( + $fh, + $internalDir . $file->getName(), + $file->getSize(), + $file->getMTime() + ); + fclose($fh); + } elseif ($file instanceof Folder) { + if ($file->isReadable()) { + $this->addDirRecursive($dir . '/' . $file->getName(), $internalDir); + } + } + } + } + + /** + * Add a file to the archive at the specified location and file name. + * + * @param string $stream Stream to read data from + * @param string $internalName Filepath and name to be used in the archive. + * @param int $size Filesize + * @param int|bool $time File mtime as int, or false + * @return bool $success + */ + public function addFileFromStream($stream, $internalName, $size, $time) { + $options = []; + if ($time) { + $options = [ + 'timestamp' => $time + ]; + } + + if ($this->streamerInstance instanceof ZipStreamer) { + return $this->streamerInstance->addFileFromStream($stream, $internalName, $options); + } else { + return $this->streamerInstance->addFileFromStream($stream, $internalName, $size, $options); + } + } + + /** + * Add an empty directory entry to the archive. + * + * @param string $dirName Directory Path and name to be added to the archive. + * @return bool $success + */ + public function addEmptyDir($dirName) { + return $this->streamerInstance->addEmptyDir($dirName); + } + + /** + * Close the archive. + * A closed archive can no longer have new files added to it. After + * closing, the file is completely written to the output stream. + * @return bool $success + */ + public function finalize() { + return $this->streamerInstance->finalize(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SubAdmin.php b/docker/overlays/nextcloud/html/lib/private/SubAdmin.php new file mode 100644 index 0000000..89fd63b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SubAdmin.php @@ -0,0 +1,306 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Lukas Reschke + * @author Mikael Hammarin + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\Hooks\PublicEmitter; +use OCP\Group\ISubAdmin; +use OCP\IDBConnection; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; + +class SubAdmin extends PublicEmitter implements ISubAdmin { + + /** @var IUserManager */ + private $userManager; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IDBConnection */ + private $dbConn; + + /** + * @param IUserManager $userManager + * @param IGroupManager $groupManager + * @param IDBConnection $dbConn + */ + public function __construct(IUserManager $userManager, + IGroupManager $groupManager, + IDBConnection $dbConn) { + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->dbConn = $dbConn; + + $this->userManager->listen('\OC\User', 'postDelete', function ($user) { + $this->post_deleteUser($user); + }); + $this->groupManager->listen('\OC\Group', 'postDelete', function ($group) { + $this->post_deleteGroup($group); + }); + } + + /** + * add a SubAdmin + * @param IUser $user user to be SubAdmin + * @param IGroup $group group $user becomes subadmin of + */ + public function createSubAdmin(IUser $user, IGroup $group): void { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->insert('group_admin') + ->values([ + 'gid' => $qb->createNamedParameter($group->getGID()), + 'uid' => $qb->createNamedParameter($user->getUID()) + ]) + ->execute(); + + $this->emit('\OC\SubAdmin', 'postCreateSubAdmin', [$user, $group]); + \OC_Hook::emit("OC_SubAdmin", "post_createSubAdmin", ["gid" => $group->getGID()]); + } + + /** + * delete a SubAdmin + * @param IUser $user the user that is the SubAdmin + * @param IGroup $group the group + */ + public function deleteSubAdmin(IUser $user, IGroup $group): void { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->delete('group_admin') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID()))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) + ->execute(); + + $this->emit('\OC\SubAdmin', 'postDeleteSubAdmin', [$user, $group]); + \OC_Hook::emit("OC_SubAdmin", "post_deleteSubAdmin", ["gid" => $group->getGID()]); + } + + /** + * get groups of a SubAdmin + * @param IUser $user the SubAdmin + * @return IGroup[] + */ + public function getSubAdminsGroups(IUser $user): array { + $groupIds = $this->getSubAdminsGroupIds($user); + + $groups = []; + foreach ($groupIds as $groupId) { + $group = $this->groupManager->get($groupId); + if ($group !== null) { + $groups[$group->getGID()] = $group; + } + } + + return $groups; + } + + /** + * Get group ids of a SubAdmin + * @param IUser $user the SubAdmin + * @return string[] + */ + public function getSubAdminsGroupIds(IUser $user): array { + $qb = $this->dbConn->getQueryBuilder(); + + $result = $qb->select('gid') + ->from('group_admin') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) + ->execute(); + + $groups = []; + while ($row = $result->fetch()) { + $groups[] = $row['gid']; + } + $result->closeCursor(); + + return $groups; + } + + /** + * get an array of groupid and displayName for a user + * @param IUser $user + * @return array ['displayName' => displayname] + */ + public function getSubAdminsGroupsName(IUser $user): array { + return array_map(function ($group) { + return ['displayName' => $group->getDisplayName()]; + }, $this->getSubAdminsGroups($user)); + } + + /** + * get SubAdmins of a group + * @param IGroup $group the group + * @return IUser[] + */ + public function getGroupsSubAdmins(IGroup $group): array { + $qb = $this->dbConn->getQueryBuilder(); + + $result = $qb->select('uid') + ->from('group_admin') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID()))) + ->execute(); + + $users = []; + while ($row = $result->fetch()) { + $user = $this->userManager->get($row['uid']); + if (!is_null($user)) { + $users[] = $user; + } + } + $result->closeCursor(); + + return $users; + } + + /** + * get all SubAdmins + * @return array + */ + public function getAllSubAdmins(): array { + $qb = $this->dbConn->getQueryBuilder(); + + $result = $qb->select('*') + ->from('group_admin') + ->execute(); + + $subadmins = []; + while ($row = $result->fetch()) { + $user = $this->userManager->get($row['uid']); + $group = $this->groupManager->get($row['gid']); + if (!is_null($user) && !is_null($group)) { + $subadmins[] = [ + 'user' => $user, + 'group' => $group + ]; + } + } + $result->closeCursor(); + + return $subadmins; + } + + /** + * checks if a user is a SubAdmin of a group + * @param IUser $user + * @param IGroup $group + * @return bool + */ + public function isSubAdminOfGroup(IUser $user, IGroup $group): bool { + $qb = $this->dbConn->getQueryBuilder(); + + /* + * Primary key is ('gid', 'uid') so max 1 result possible here + */ + $result = $qb->select('*') + ->from('group_admin') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID()))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) + ->execute(); + + $fetch = $result->fetch(); + $result->closeCursor(); + $result = !empty($fetch) ? true : false; + + return $result; + } + + /** + * checks if a user is a SubAdmin + * @param IUser $user + * @return bool + */ + public function isSubAdmin(IUser $user): bool { + // Check if the user is already an admin + if ($this->groupManager->isAdmin($user->getUID())) { + return true; + } + + $qb = $this->dbConn->getQueryBuilder(); + + $result = $qb->select('gid') + ->from('group_admin') + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) + ->setMaxResults(1) + ->execute(); + + $isSubAdmin = $result->fetch(); + $result->closeCursor(); + + return $isSubAdmin !== false; + } + + /** + * checks if a user is a accessible by a subadmin + * @param IUser $subadmin + * @param IUser $user + * @return bool + */ + public function isUserAccessible(IUser $subadmin, IUser $user): bool { + if (!$this->isSubAdmin($subadmin)) { + return false; + } + if ($this->groupManager->isAdmin($user->getUID())) { + return false; + } + + $accessibleGroups = $this->getSubAdminsGroupIds($subadmin); + $userGroups = $this->groupManager->getUserGroupIds($user); + + return !empty(array_intersect($accessibleGroups, $userGroups)); + } + + /** + * delete all SubAdmins by $user + * @param IUser $user + */ + private function post_deleteUser(IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->delete('group_admin') + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) + ->execute(); + } + + /** + * delete all SubAdmins by $group + * @param IGroup $group + */ + private function post_deleteGroup(IGroup $group) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->delete('group_admin') + ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID()))) + ->execute(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Support/CrashReport/Registry.php b/docker/overlays/nextcloud/html/lib/private/Support/CrashReport/Registry.php new file mode 100644 index 0000000..bff6270 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Support/CrashReport/Registry.php @@ -0,0 +1,157 @@ + + * @author Morris Jobke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Support\CrashReport; + +use Exception; +use OCP\AppFramework\QueryException; +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\Support\CrashReport\ICollectBreadcrumbs; +use OCP\Support\CrashReport\IMessageReporter; +use OCP\Support\CrashReport\IRegistry; +use OCP\Support\CrashReport\IReporter; +use Throwable; + +class Registry implements IRegistry { + + /** @var string[] */ + private $lazyReporters = []; + + /** @var IReporter[] */ + private $reporters = []; + + /** @var IServerContainer */ + private $serverContainer; + + /** @var ILogger */ + private $logger; + + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + /** + * Register a reporter instance + * + * @param IReporter $reporter + */ + public function register(IReporter $reporter): void { + $this->reporters[] = $reporter; + } + + public function registerLazy(string $class): void { + $this->lazyReporters[] = $class; + } + + /** + * Delegate breadcrumb collection to all registered reporters + * + * @param string $message + * @param string $category + * @param array $context + * + * @since 15.0.0 + */ + public function delegateBreadcrumb(string $message, string $category, array $context = []): void { + $this->loadLazyProviders(); + + foreach ($this->reporters as $reporter) { + if ($reporter instanceof ICollectBreadcrumbs) { + $reporter->collect($message, $category, $context); + } + } + } + + /** + * Delegate crash reporting to all registered reporters + * + * @param Exception|Throwable $exception + * @param array $context + */ + public function delegateReport($exception, array $context = []): void { + $this->loadLazyProviders(); + + foreach ($this->reporters as $reporter) { + $reporter->report($exception, $context); + } + } + + /** + * Delegate a message to all reporters that implement IMessageReporter + * + * @param string $message + * @param array $context + * + * @return void + */ + public function delegateMessage(string $message, array $context = []): void { + $this->loadLazyProviders(); + + foreach ($this->reporters as $reporter) { + if ($reporter instanceof IMessageReporter) { + $reporter->reportMessage($message, $context); + } + } + } + + private function loadLazyProviders(): void { + $classes = $this->lazyReporters; + foreach ($classes as $class) { + try { + /** @var IReporter $reporter */ + $reporter = $this->serverContainer->query($class); + } catch (QueryException $e) { + /* + * There is a circular dependency between the logger and the registry, so + * we can not inject it. Thus the static call. + */ + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Could not load lazy crash reporter: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + /** + * Try to register the loaded reporter. Theoretically it could be of a wrong + * type, so we might get a TypeError here that we should catch. + */ + try { + $this->register($reporter); + } catch (Throwable $e) { + /* + * There is a circular dependency between the logger and the registry, so + * we can not inject it. Thus the static call. + */ + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Could not register lazy crash reporter: ' . $e->getMessage(), + 'level' => ILogger::FATAL, + ]); + } + } + $this->lazyReporters = []; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Support/Subscription/Registry.php b/docker/overlays/nextcloud/html/lib/private/Support/Subscription/Registry.php new file mode 100644 index 0000000..3d6a9b0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Support/Subscription/Registry.php @@ -0,0 +1,135 @@ + + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Support\Subscription; + +use OCP\AppFramework\QueryException; +use OCP\IConfig; +use OCP\IServerContainer; +use OCP\Support\Subscription\Exception\AlreadyRegisteredException; +use OCP\Support\Subscription\IRegistry; +use OCP\Support\Subscription\ISubscription; +use OCP\Support\Subscription\ISupportedApps; + +class Registry implements IRegistry { + + /** @var ISubscription */ + private $subscription = null; + + /** @var string */ + private $subscriptionService = null; + + /** @var IConfig */ + private $config; + + /** @var IServerContainer */ + private $container; + + public function __construct(IConfig $config, IServerContainer $container) { + $this->config = $config; + $this->container = $container; + } + + private function getSubscription(): ?ISubscription { + if ($this->subscription === null && $this->subscriptionService !== null) { + try { + $this->subscription = $this->container->query($this->subscriptionService); + } catch (QueryException $e) { + // Ignore this + } + } + + return $this->subscription; + } + + /** + * Register a subscription instance. In case it is called multiple times the + * first one is used. + * + * @param ISubscription $subscription + * @throws AlreadyRegisteredException + * + * @since 17.0.0 + */ + public function register(ISubscription $subscription): void { + if ($this->subscription !== null || $this->subscriptionService !== null) { + throw new AlreadyRegisteredException(); + } + $this->subscription = $subscription; + } + + public function registerService(string $subscriptionService): void { + if ($this->subscription !== null || $this->subscriptionService !== null) { + throw new AlreadyRegisteredException(); + } + + $this->subscriptionService = $subscriptionService; + } + + + /** + * Fetches the list of app IDs that are supported by the subscription + * + * @since 17.0.0 + */ + public function delegateGetSupportedApps(): array { + if ($this->getSubscription() instanceof ISupportedApps) { + return $this->getSubscription()->getSupportedApps(); + } + return []; + } + + /** + * Indicates if a valid subscription is available + * + * @since 17.0.0 + */ + public function delegateHasValidSubscription(): bool { + // Allow overwriting this manually for environments where the subscription information cannot be fetched + if ($this->config->getSystemValueBool('has_valid_subscription')) { + return true; + } + + if ($this->getSubscription() instanceof ISubscription) { + return $this->getSubscription()->hasValidSubscription(); + } + return false; + } + + /** + * Indicates if the subscription has extended support + * + * @since 17.0.0 + */ + public function delegateHasExtendedSupport(): bool { + if ($this->getSubscription() instanceof ISubscription && method_exists($this->subscription, 'hasExtendedSupport')) { + return $this->getSubscription()->hasExtendedSupport(); + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SystemConfig.php b/docker/overlays/nextcloud/html/lib/private/SystemConfig.php new file mode 100644 index 0000000..7f0114e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SystemConfig.php @@ -0,0 +1,181 @@ + + * @author Joas Schilling + * @author Johannes Schlichenmaier + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCP\IConfig; + +/** + * Class which provides access to the system config values stored in config.php + * Internal class for bootstrap only. + * fixes cyclic DI: AllConfig needs AppConfig needs Database needs AllConfig + */ +class SystemConfig { + + /** @var array */ + protected $sensitiveValues = [ + 'instanceid' => true, + 'datadirectory' => true, + 'dbname' => true, + 'dbhost' => true, + 'dbpassword' => true, + 'dbuser' => true, + 'mail_from_address' => true, + 'mail_domain' => true, + 'mail_smtphost' => true, + 'mail_smtpname' => true, + 'mail_smtppassword' => true, + 'passwordsalt' => true, + 'secret' => true, + 'updater.secret' => true, + 'trusted_proxies' => true, + 'proxyuserpwd' => true, + 'log.condition' => [ + 'shared_secret' => true, + ], + 'license-key' => true, + 'redis' => [ + 'host' => true, + 'password' => true, + ], + 'objectstore' => [ + 'arguments' => [ + // Legacy Swift (https://github.com/nextcloud/server/pull/17696#discussion_r341302207) + 'options' => [ + 'credentials' => [ + 'key' => true, + 'secret' => true, + ] + ], + // S3 + 'key' => true, + 'secret' => true, + // Swift v2 + 'username' => true, + 'password' => true, + // Swift v3 + 'user' => [ + 'name' => true, + 'password' => true, + ], + ], + ], + ]; + + /** @var Config */ + private $config; + + public function __construct(Config $config) { + $this->config = $config; + } + + /** + * Lists all available config keys + * @return array an array of key names + */ + public function getKeys() { + return $this->config->getKeys(); + } + + /** + * Sets a new system wide value + * + * @param string $key the key of the value, under which will be saved + * @param mixed $value the value that should be stored + */ + public function setValue($key, $value) { + $this->config->setValue($key, $value); + } + + /** + * Sets and deletes values and writes the config.php + * + * @param array $configs Associative array with `key => value` pairs + * If value is null, the config key will be deleted + */ + public function setValues(array $configs) { + $this->config->setValues($configs); + } + + /** + * Looks up a system wide defined value + * + * @param string $key the key of the value, under which it was saved + * @param mixed $default the default value to be returned if the value isn't set + * @return mixed the value or $default + */ + public function getValue($key, $default = '') { + return $this->config->getValue($key, $default); + } + + /** + * Looks up a system wide defined value and filters out sensitive data + * + * @param string $key the key of the value, under which it was saved + * @param mixed $default the default value to be returned if the value isn't set + * @return mixed the value or $default + */ + public function getFilteredValue($key, $default = '') { + $value = $this->getValue($key, $default); + + if (isset($this->sensitiveValues[$key])) { + $value = $this->removeSensitiveValue($this->sensitiveValues[$key], $value); + } + + return $value; + } + + /** + * Delete a system wide defined value + * + * @param string $key the key of the value, under which it was saved + */ + public function deleteValue($key) { + $this->config->deleteKey($key); + } + + /** + * @param bool|array $keysToRemove + * @param mixed $value + * @return mixed + */ + protected function removeSensitiveValue($keysToRemove, $value) { + if ($keysToRemove === true) { + return IConfig::SENSITIVE_VALUE; + } + + if (is_array($value)) { + foreach ($keysToRemove as $keyToRemove => $valueToRemove) { + if (isset($value[$keyToRemove])) { + $value[$keyToRemove] = $this->removeSensitiveValue($valueToRemove, $value[$keyToRemove]); + } + } + } + + return $value; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SystemTag/ManagerFactory.php b/docker/overlays/nextcloud/html/lib/private/SystemTag/ManagerFactory.php new file mode 100644 index 0000000..a75a090 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SystemTag/ManagerFactory.php @@ -0,0 +1,87 @@ + + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\SystemTag; + +use OCP\IServerContainer; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagManagerFactory; +use OCP\SystemTag\ISystemTagObjectMapper; + +/** + * Default factory class for system tag managers + * + * @package OCP\SystemTag + * @since 9.0.0 + */ +class ManagerFactory implements ISystemTagManagerFactory { + + /** + * Server container + * + * @var IServerContainer + */ + private $serverContainer; + + /** + * Constructor for the system tag manager factory + * + * @param IServerContainer $serverContainer server container + */ + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + /** + * Creates and returns an instance of the system tag manager + * + * @return ISystemTagManager + * @since 9.0.0 + */ + public function getManager(): ISystemTagManager { + return new SystemTagManager( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getGroupManager(), + $this->serverContainer->getEventDispatcher() + ); + } + + /** + * Creates and returns an instance of the system tag object + * mapper + * + * @return ISystemTagObjectMapper + * @since 9.0.0 + */ + public function getObjectMapper(): ISystemTagObjectMapper { + return new SystemTagObjectMapper( + $this->serverContainer->getDatabaseConnection(), + $this->getManager(), + $this->serverContainer->getEventDispatcher() + ); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTag.php b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTag.php new file mode 100644 index 0000000..14ccaff --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTag.php @@ -0,0 +1,96 @@ + + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\SystemTag; + +use OCP\SystemTag\ISystemTag; + +class SystemTag implements ISystemTag { + + /** + * @var string + */ + private $id; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $userVisible; + + /** + * @var bool + */ + private $userAssignable; + + /** + * Constructor. + * + * @param string $id tag id + * @param string $name tag name + * @param bool $userVisible whether the tag is user visible + * @param bool $userAssignable whether the tag is user assignable + */ + public function __construct(string $id, string $name, bool $userVisible, bool $userAssignable) { + $this->id = $id; + $this->name = $name; + $this->userVisible = $userVisible; + $this->userAssignable = $userAssignable; + } + + /** + * {@inheritdoc} + */ + public function getId(): string { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getName(): string { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function isUserVisible(): bool { + return $this->userVisible; + } + + /** + * {@inheritdoc} + */ + public function isUserAssignable(): bool { + return $this->userAssignable; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagManager.php b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagManager.php new file mode 100644 index 0000000..26ed2f2 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagManager.php @@ -0,0 +1,434 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\SystemTag; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ManagerEvent; +use OCP\SystemTag\TagAlreadyExistsException; +use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Manager class for system tags + */ +class SystemTagManager implements ISystemTagManager { + public const TAG_TABLE = 'systemtag'; + public const TAG_GROUP_TABLE = 'systemtag_group'; + + /** @var IDBConnection */ + protected $connection; + + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** @var IGroupManager */ + protected $groupManager; + + /** + * Prepared query for selecting tags directly + * + * @var \OCP\DB\QueryBuilder\IQueryBuilder + */ + private $selectTagQuery; + + /** + * Constructor. + * + * @param IDBConnection $connection database connection + * @param IGroupManager $groupManager + * @param EventDispatcherInterface $dispatcher + */ + public function __construct( + IDBConnection $connection, + IGroupManager $groupManager, + EventDispatcherInterface $dispatcher + ) { + $this->connection = $connection; + $this->groupManager = $groupManager; + $this->dispatcher = $dispatcher; + + $query = $this->connection->getQueryBuilder(); + $this->selectTagQuery = $query->select('*') + ->from(self::TAG_TABLE) + ->where($query->expr()->eq('name', $query->createParameter('name'))) + ->andWhere($query->expr()->eq('visibility', $query->createParameter('visibility'))) + ->andWhere($query->expr()->eq('editable', $query->createParameter('editable'))); + } + + /** + * {@inheritdoc} + */ + public function getTagsByIds($tagIds): array { + if (!\is_array($tagIds)) { + $tagIds = [$tagIds]; + } + + $tags = []; + + // note: not all databases will fail if it's a string or starts with a number + foreach ($tagIds as $tagId) { + if (!is_numeric($tagId)) { + throw new \InvalidArgumentException('Tag id must be integer'); + } + } + + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from(self::TAG_TABLE) + ->where($query->expr()->in('id', $query->createParameter('tagids'))) + ->addOrderBy('name', 'ASC') + ->addOrderBy('visibility', 'ASC') + ->addOrderBy('editable', 'ASC') + ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $tags[$row['id']] = $this->createSystemTagFromRow($row); + } + + $result->closeCursor(); + + if (\count($tags) !== \count($tagIds)) { + throw new TagNotFoundException( + 'Tag id(s) not found', 0, null, array_diff($tagIds, array_keys($tags)) + ); + } + + return $tags; + } + + /** + * {@inheritdoc} + */ + public function getAllTags($visibilityFilter = null, $nameSearchPattern = null): array { + $tags = []; + + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from(self::TAG_TABLE); + + if (!\is_null($visibilityFilter)) { + $query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter))); + } + + if (!empty($nameSearchPattern)) { + $query->andWhere( + $query->expr()->like( + 'name', + $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern). '%') + ) + ); + } + + $query + ->addOrderBy('name', 'ASC') + ->addOrderBy('visibility', 'ASC') + ->addOrderBy('editable', 'ASC'); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $tags[$row['id']] = $this->createSystemTagFromRow($row); + } + + $result->closeCursor(); + + return $tags; + } + + /** + * {@inheritdoc} + */ + public function getTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag { + $result = $this->selectTagQuery + ->setParameter('name', $tagName) + ->setParameter('visibility', $userVisible ? 1 : 0) + ->setParameter('editable', $userAssignable ? 1 : 0) + ->execute(); + + $row = $result->fetch(); + $result->closeCursor(); + if (!$row) { + throw new TagNotFoundException( + 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') does not exist' + ); + } + + return $this->createSystemTagFromRow($row); + } + + /** + * {@inheritdoc} + */ + public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag { + $query = $this->connection->getQueryBuilder(); + $query->insert(self::TAG_TABLE) + ->values([ + 'name' => $query->createNamedParameter($tagName), + 'visibility' => $query->createNamedParameter($userVisible ? 1 : 0), + 'editable' => $query->createNamedParameter($userAssignable ? 1 : 0), + ]); + + try { + $query->execute(); + } catch (UniqueConstraintViolationException $e) { + throw new TagAlreadyExistsException( + 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', + 0, + $e + ); + } + + $tagId = $query->getLastInsertId(); + + $tag = new SystemTag( + (string)$tagId, + $tagName, + $userVisible, + $userAssignable + ); + + $this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent( + ManagerEvent::EVENT_CREATE, $tag + )); + + return $tag; + } + + /** + * {@inheritdoc} + */ + public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable) { + try { + $tags = $this->getTagsByIds($tagId); + } catch (TagNotFoundException $e) { + throw new TagNotFoundException( + 'Tag does not exist', 0, null, [$tagId] + ); + } + + $beforeUpdate = array_shift($tags); + $afterUpdate = new SystemTag( + $tagId, + $newName, + $userVisible, + $userAssignable + ); + + $query = $this->connection->getQueryBuilder(); + $query->update(self::TAG_TABLE) + ->set('name', $query->createParameter('name')) + ->set('visibility', $query->createParameter('visibility')) + ->set('editable', $query->createParameter('editable')) + ->where($query->expr()->eq('id', $query->createParameter('tagid'))) + ->setParameter('name', $newName) + ->setParameter('visibility', $userVisible ? 1 : 0) + ->setParameter('editable', $userAssignable ? 1 : 0) + ->setParameter('tagid', $tagId); + + try { + if ($query->execute() === 0) { + throw new TagNotFoundException( + 'Tag does not exist', 0, null, [$tagId] + ); + } + } catch (UniqueConstraintViolationException $e) { + throw new TagAlreadyExistsException( + 'Tag ("' . $newName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', + 0, + $e + ); + } + + $this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent( + ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate + )); + } + + /** + * {@inheritdoc} + */ + public function deleteTags($tagIds) { + if (!\is_array($tagIds)) { + $tagIds = [$tagIds]; + } + + $tagNotFoundException = null; + $tags = []; + try { + $tags = $this->getTagsByIds($tagIds); + } catch (TagNotFoundException $e) { + $tagNotFoundException = $e; + + // Get existing tag objects for the hooks later + $existingTags = array_diff($tagIds, $tagNotFoundException->getMissingTags()); + if (!empty($existingTags)) { + try { + $tags = $this->getTagsByIds($existingTags); + } catch (TagNotFoundException $e) { + // Ignore further errors... + } + } + } + + // delete relationships first + $query = $this->connection->getQueryBuilder(); + $query->delete(SystemTagObjectMapper::RELATION_TABLE) + ->where($query->expr()->in('systemtagid', $query->createParameter('tagids'))) + ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY) + ->execute(); + + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TAG_TABLE) + ->where($query->expr()->in('id', $query->createParameter('tagids'))) + ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY) + ->execute(); + + foreach ($tags as $tag) { + $this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent( + ManagerEvent::EVENT_DELETE, $tag + )); + } + + if ($tagNotFoundException !== null) { + throw new TagNotFoundException( + 'Tag id(s) not found', 0, $tagNotFoundException, $tagNotFoundException->getMissingTags() + ); + } + } + + /** + * {@inheritdoc} + */ + public function canUserAssignTag(ISystemTag $tag, IUser $user): bool { + // early check to avoid unneeded group lookups + if ($tag->isUserAssignable() && $tag->isUserVisible()) { + return true; + } + + if ($this->groupManager->isAdmin($user->getUID())) { + return true; + } + + if (!$tag->isUserVisible()) { + return false; + } + + $groupIds = $this->groupManager->getUserGroupIds($user); + if (!empty($groupIds)) { + $matchingGroups = array_intersect($groupIds, $this->getTagGroups($tag)); + if (!empty($matchingGroups)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function canUserSeeTag(ISystemTag $tag, IUser $user): bool { + if ($tag->isUserVisible()) { + return true; + } + + if ($this->groupManager->isAdmin($user->getUID())) { + return true; + } + + return false; + } + + private function createSystemTagFromRow($row) { + return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']); + } + + /** + * {@inheritdoc} + */ + public function setTagGroups(ISystemTag $tag, array $groupIds) { + // delete relationships first + $this->connection->beginTransaction(); + try { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TAG_GROUP_TABLE) + ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId()))) + ->execute(); + + // add each group id + $query = $this->connection->getQueryBuilder(); + $query->insert(self::TAG_GROUP_TABLE) + ->values([ + 'systemtagid' => $query->createNamedParameter($tag->getId()), + 'gid' => $query->createParameter('gid'), + ]); + foreach ($groupIds as $groupId) { + if ($groupId === '') { + continue; + } + $query->setParameter('gid', $groupId); + $query->execute(); + } + + $this->connection->commit(); + } catch (\Exception $e) { + $this->connection->rollBack(); + throw $e; + } + } + + /** + * {@inheritdoc} + */ + public function getTagGroups(ISystemTag $tag): array { + $groupIds = []; + $query = $this->connection->getQueryBuilder(); + $query->select('gid') + ->from(self::TAG_GROUP_TABLE) + ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId()))) + ->orderBy('gid'); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $groupIds[] = $row['gid']; + } + + $result->closeCursor(); + + return $groupIds; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagObjectMapper.php b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagObjectMapper.php new file mode 100644 index 0000000..5403321 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/SystemTag/SystemTagObjectMapper.php @@ -0,0 +1,273 @@ + + * @author Joas Schilling + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\SystemTag; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\MapperEvent; +use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class SystemTagObjectMapper implements ISystemTagObjectMapper { + public const RELATION_TABLE = 'systemtag_object_mapping'; + + /** @var ISystemTagManager */ + protected $tagManager; + + /** @var IDBConnection */ + protected $connection; + + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** + * Constructor. + * + * @param IDBConnection $connection database connection + * @param ISystemTagManager $tagManager system tag manager + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(IDBConnection $connection, ISystemTagManager $tagManager, EventDispatcherInterface $dispatcher) { + $this->connection = $connection; + $this->tagManager = $tagManager; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function getTagIdsForObjects($objIds, string $objectType): array { + if (!\is_array($objIds)) { + $objIds = [$objIds]; + } elseif (empty($objIds)) { + return []; + } + + $query = $this->connection->getQueryBuilder(); + $query->select(['systemtagid', 'objectid']) + ->from(self::RELATION_TABLE) + ->where($query->expr()->in('objectid', $query->createParameter('objectids'))) + ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype'))) + ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_INT_ARRAY) + ->setParameter('objecttype', $objectType) + ->addOrderBy('objectid', 'ASC') + ->addOrderBy('systemtagid', 'ASC'); + + $mapping = []; + foreach ($objIds as $objId) { + $mapping[$objId] = []; + } + + $result = $query->execute(); + while ($row = $result->fetch()) { + $objectId = $row['objectid']; + $mapping[$objectId][] = $row['systemtagid']; + } + + $result->closeCursor(); + + return $mapping; + } + + /** + * {@inheritdoc} + */ + public function getObjectIdsForTags($tagIds, string $objectType, int $limit = 0, string $offset = ''): array { + if (!\is_array($tagIds)) { + $tagIds = [$tagIds]; + } + + $this->assertTagsExist($tagIds); + + $query = $this->connection->getQueryBuilder(); + $query->selectDistinct('objectid') + ->from(self::RELATION_TABLE) + ->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType))); + + if ($limit) { + if (\count($tagIds) !== 1) { + throw new \InvalidArgumentException('Limit is only allowed with a single tag'); + } + + $query->setMaxResults($limit) + ->orderBy('objectid', 'ASC'); + + if ($offset !== '') { + $query->andWhere($query->expr()->gt('objectid', $query->createNamedParameter($offset))); + } + } + + $objectIds = []; + + $result = $query->execute(); + while ($row = $result->fetch()) { + $objectIds[] = $row['objectid']; + } + + return $objectIds; + } + + /** + * {@inheritdoc} + */ + public function assignTags(string $objId, string $objectType, $tagIds) { + if (!\is_array($tagIds)) { + $tagIds = [$tagIds]; + } + + $this->assertTagsExist($tagIds); + + $query = $this->connection->getQueryBuilder(); + $query->insert(self::RELATION_TABLE) + ->values([ + 'objectid' => $query->createNamedParameter($objId), + 'objecttype' => $query->createNamedParameter($objectType), + 'systemtagid' => $query->createParameter('tagid'), + ]); + + $tagsAssigned = []; + foreach ($tagIds as $tagId) { + try { + $query->setParameter('tagid', $tagId); + $query->execute(); + $tagsAssigned[] = $tagId; + } catch (UniqueConstraintViolationException $e) { + // ignore existing relations + } + } + + if (empty($tagsAssigned)) { + return; + } + + $this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent( + MapperEvent::EVENT_ASSIGN, + $objectType, + $objId, + $tagsAssigned + )); + } + + /** + * {@inheritdoc} + */ + public function unassignTags(string $objId, string $objectType, $tagIds) { + if (!\is_array($tagIds)) { + $tagIds = [$tagIds]; + } + + $this->assertTagsExist($tagIds); + + $query = $this->connection->getQueryBuilder(); + $query->delete(self::RELATION_TABLE) + ->where($query->expr()->eq('objectid', $query->createParameter('objectid'))) + ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype'))) + ->andWhere($query->expr()->in('systemtagid', $query->createParameter('tagids'))) + ->setParameter('objectid', $objId) + ->setParameter('objecttype', $objectType) + ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY) + ->execute(); + + $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent( + MapperEvent::EVENT_UNASSIGN, + $objectType, + $objId, + $tagIds + )); + } + + /** + * {@inheritdoc} + */ + public function haveTag($objIds, string $objectType, string $tagId, bool $all = true): bool { + $this->assertTagsExist([$tagId]); + + if (!\is_array($objIds)) { + $objIds = [$objIds]; + } + + $query = $this->connection->getQueryBuilder(); + + if (!$all) { + // If we only need one entry, we make the query lighter, by not + // counting the elements + $query->select('*') + ->setMaxResults(1); + } else { + $query->select($query->func()->count($query->expr()->literal(1))); + } + + $query->from(self::RELATION_TABLE) + ->where($query->expr()->in('objectid', $query->createParameter('objectids'))) + ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype'))) + ->andWhere($query->expr()->eq('systemtagid', $query->createParameter('tagid'))) + ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY) + ->setParameter('tagid', $tagId) + ->setParameter('objecttype', $objectType); + + $result = $query->execute(); + $row = $result->fetch(\PDO::FETCH_NUM); + $result->closeCursor(); + + if ($all) { + return ((int)$row[0] === \count($objIds)); + } + + return (bool) $row; + } + + /** + * Asserts that all the given tag ids exist. + * + * @param string[] $tagIds tag ids to check + * + * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist + */ + private function assertTagsExist($tagIds) { + $tags = $this->tagManager->getTagsByIds($tagIds); + if (\count($tags) !== \count($tagIds)) { + // at least one tag missing, bail out + $foundTagIds = array_map( + function (ISystemTag $tag) { + return $tag->getId(); + }, + $tags + ); + $missingTagIds = array_diff($tagIds, $foundTagIds); + throw new TagNotFoundException( + 'Tags not found', 0, null, $missingTagIds + ); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/TagManager.php b/docker/overlays/nextcloud/html/lib/private/TagManager.php new file mode 100644 index 0000000..4613b62 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/TagManager.php @@ -0,0 +1,107 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Factory class creating instances of \OCP\ITags + * + * A tag can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or + * anything else that is either parsed from a vobject or that the user chooses + * to add. + * Tag names are not case-sensitive, but will be saved with the case they + * are entered in. If a user already has a tag 'family' for a type, and + * tries to add a tag named 'Family' it will be silently ignored. + */ + +namespace OC; + +use OC\Tagging\TagMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\ITagManager; +use OCP\ITags; +use OCP\IUserSession; + +class TagManager implements ITagManager { + + /** @var TagMapper */ + private $mapper; + + /** @var IUserSession */ + private $userSession; + + /** @var IDBConnection */ + private $connection; + + public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) { + $this->mapper = $mapper; + $this->userSession = $userSession; + $this->connection = $connection; + } + + /** + * Create a new \OCP\ITags instance and load tags from db. + * + * @see \OCP\ITags + * @param string $type The type identifier e.g. 'contact' or 'event'. + * @param array $defaultTags An array of default tags to be used if none are stored. + * @param boolean $includeShared Whether to include tags for items shared with this user by others. + * @param string $userId user for which to retrieve the tags, defaults to the currently + * logged in user + * @return \OCP\ITags + * + * since 20.0.0 $includeShared isn't used anymore + */ + public function load($type, $defaultTags = [], $includeShared = false, $userId = null) { + if (is_null($userId)) { + $user = $this->userSession->getUser(); + if ($user === null) { + // nothing we can do without a user + return null; + } + $userId = $this->userSession->getUser()->getUId(); + } + return new Tags($this->mapper, $userId, $type, $defaultTags); + } + + /** + * Get all users who favorited an object + * + * @param string $objectType + * @param int $objectId + * @return array + */ + public function getUsersFavoritingObject(string $objectType, int $objectId): array { + $query = $this->connection->getQueryBuilder(); + $query->select('uid') + ->from('vcategory_to_object', 'o') + ->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id')) + ->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE))); + + return $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Tagging/Tag.php b/docker/overlays/nextcloud/html/lib/private/Tagging/Tag.php new file mode 100644 index 0000000..55fa996 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Tagging/Tag.php @@ -0,0 +1,91 @@ + + * @author Christoph Wurst + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Tagging; + +use OCP\AppFramework\Db\Entity; + +/** + * Class to represent a tag. + * + * @method string getOwner() + * @method void setOwner(string $owner) + * @method string getType() + * @method void setType(string $type) + * @method string getName() + * @method void setName(string $name) + */ +class Tag extends Entity { + protected $owner; + protected $type; + protected $name; + + /** + * Constructor. + * + * @param string $owner The tag's owner + * @param string $type The type of item this tag is used for + * @param string $name The tag's name + */ + public function __construct($owner = null, $type = null, $name = null) { + $this->setOwner($owner); + $this->setType($type); + $this->setName($name); + } + + /** + * Transform a database columnname to a property + * + * @param string $columnName the name of the column + * @return string the property name + * @todo migrate existing database columns to the correct names + * to be able to drop this direct mapping + */ + public function columnToProperty($columnName) { + if ($columnName === 'category') { + return 'name'; + } elseif ($columnName === 'uid') { + return 'owner'; + } else { + return parent::columnToProperty($columnName); + } + } + + /** + * Transform a property to a database column name + * + * @param string $property the name of the property + * @return string the column name + */ + public function propertyToColumn($property) { + if ($property === 'name') { + return 'category'; + } elseif ($property === 'owner') { + return 'uid'; + } else { + return parent::propertyToColumn($property); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Tagging/TagMapper.php b/docker/overlays/nextcloud/html/lib/private/Tagging/TagMapper.php new file mode 100644 index 0000000..d9c8a7f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Tagging/TagMapper.php @@ -0,0 +1,80 @@ + + * @author Bernhard Reiter + * @author Christoph Wurst + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Tagging; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; + +/** + * Mapper for Tag entity + */ +class TagMapper extends Mapper { + + /** + * Constructor. + * + * @param IDBConnection $db Instance of the Db abstraction layer. + */ + public function __construct(IDBConnection $db) { + parent::__construct($db, 'vcategory', Tag::class); + } + + /** + * Load tags from the database. + * + * @param array|string $owners The user(s) whose tags we are going to load. + * @param string $type The type of item for which we are loading tags. + * @return array An array of Tag objects. + */ + public function loadTags($owners, $type) { + if (!is_array($owners)) { + $owners = [$owners]; + } + + $sql = 'SELECT `id`, `uid`, `type`, `category` FROM `' . $this->getTableName() . '` ' + . 'WHERE `uid` IN (' . str_repeat('?,', count($owners)-1) . '?) AND `type` = ? ORDER BY `category`'; + return $this->findEntities($sql, array_merge($owners, [$type])); + } + + /** + * Check if a given Tag object already exists in the database. + * + * @param Tag $tag The tag to look for in the database. + * @return bool + */ + public function tagExists($tag) { + $sql = 'SELECT `id`, `uid`, `type`, `category` FROM `' . $this->getTableName() . '` ' + . 'WHERE `uid` = ? AND `type` = ? AND `category` = ?'; + try { + $this->findEntity($sql, [$tag->getOwner(), $tag->getType(), $tag->getName()]); + } catch (DoesNotExistException $e) { + return false; + } + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Tags.php b/docker/overlays/nextcloud/html/lib/private/Tags.php new file mode 100644 index 0000000..3fc66c6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Tags.php @@ -0,0 +1,839 @@ + + * @author Bernhard Reiter + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author derkostka + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class for easily tagging objects by their id + * + * A tag can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or + * anything else that is either parsed from a vobject or that the user chooses + * to add. + * Tag names are not case-sensitive, but will be saved with the case they + * are entered in. If a user already has a tag 'family' for a type, and + * tries to add a tag named 'Family' it will be silently ignored. + */ + +namespace OC; + +use OC\Tagging\Tag; +use OC\Tagging\TagMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\ILogger; +use OCP\ITags; + +class Tags implements ITags { + + /** + * Tags + * + * @var array + */ + private $tags = []; + + /** + * Used for storing objectid/categoryname pairs while rescanning. + * + * @var array + */ + private static $relations = []; + + /** + * Type + * + * @var string + */ + private $type; + + /** + * User + * + * @var string + */ + private $user; + + /** + * Are we including tags for shared items? + * + * @var bool + */ + private $includeShared = false; + + /** + * The current user, plus any owners of the items shared with the current + * user, if $this->includeShared === true. + * + * @var array + */ + private $owners = []; + + /** + * The Mapper we're using to communicate our Tag objects to the database. + * + * @var TagMapper + */ + private $mapper; + + /** + * The sharing backend for objects of $this->type. Required if + * $this->includeShared === true to determine ownership of items. + * + * @var \OCP\Share_Backend + */ + private $backend; + + public const TAG_TABLE = '*PREFIX*vcategory'; + public const RELATION_TABLE = '*PREFIX*vcategory_to_object'; + + /** + * Constructor. + * + * @param TagMapper $mapper Instance of the TagMapper abstraction layer. + * @param string $user The user whose data the object will operate on. + * @param string $type The type of items for which tags will be loaded. + * @param array $defaultTags Tags that should be created at construction. + * + * since 20.0.0 $includeShared isn't used anymore + */ + public function __construct(TagMapper $mapper, $user, $type, $defaultTags = []) { + $this->mapper = $mapper; + $this->user = $user; + $this->type = $type; + $this->owners = [$this->user]; + $this->tags = $this->mapper->loadTags($this->owners, $this->type); + + if (count($defaultTags) > 0 && count($this->tags) === 0) { + $this->addMultiple($defaultTags, true); + } + } + + /** + * Check if any tags are saved for this type and user. + * + * @return boolean + */ + public function isEmpty() { + return count($this->tags) === 0; + } + + /** + * Returns an array mapping a given tag's properties to its values: + * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype'] + * + * @param string $id The ID of the tag that is going to be mapped + * @return array|false + */ + public function getTag($id) { + $key = $this->getTagById($id); + if ($key !== false) { + return $this->tagMap($this->tags[$key]); + } + return false; + } + + /** + * Get the tags for a specific user. + * + * This returns an array with maps containing each tag's properties: + * [ + * ['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'], + * ['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'], + * ] + * + * @return array + */ + public function getTags() { + if (!count($this->tags)) { + return []; + } + + usort($this->tags, function ($a, $b) { + return strnatcasecmp($a->getName(), $b->getName()); + }); + $tagMap = []; + + foreach ($this->tags as $tag) { + if ($tag->getName() !== ITags::TAG_FAVORITE) { + $tagMap[] = $this->tagMap($tag); + } + } + return $tagMap; + } + + /** + * Return only the tags owned by the given user, omitting any tags shared + * by other users. + * + * @param string $user The user whose tags are to be checked. + * @return array An array of Tag objects. + */ + public function getTagsForUser($user) { + return array_filter($this->tags, + function ($tag) use ($user) { + return $tag->getOwner() === $user; + } + ); + } + + /** + * Get the list of tags for the given ids. + * + * @param array $objIds array of object ids + * @return array|boolean of tags id as key to array of tag names + * or false if an error occurred + */ + public function getTagsForObjects(array $objIds) { + $entries = []; + + try { + $conn = \OC::$server->getDatabaseConnection(); + $chunks = array_chunk($objIds, 900, false); + foreach ($chunks as $chunk) { + $result = $conn->executeQuery( + 'SELECT `category`, `categoryid`, `objid` ' . + 'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' . + 'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)', + [$this->user, $this->type, $chunk], + [null, null, IQueryBuilder::PARAM_INT_ARRAY] + ); + while ($row = $result->fetch()) { + $objId = (int)$row['objid']; + if (!isset($entries[$objId])) { + $entries[$objId] = []; + } + $entries[$objId][] = $row['category']; + } + if ($result === null) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); + return false; + } + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + + return $entries; + } + + /** + * Get the a list if items tagged with $tag. + * + * Throws an exception if the tag could not be found. + * + * @param string $tag Tag id or name. + * @return array|false An array of object ids or false on error. + * @throws \Exception + */ + public function getIdsForTag($tag) { + $result = null; + $tagId = false; + if (is_numeric($tag)) { + $tagId = $tag; + } elseif (is_string($tag)) { + $tag = trim($tag); + if ($tag === '') { + \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG); + return false; + } + $tagId = $this->getTagId($tag); + } + + if ($tagId === false) { + $l10n = \OC::$server->getL10N('core'); + throw new \Exception( + $l10n->t('Could not find category "%s"', [$tag]) + ); + } + + $ids = []; + $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE + . '` WHERE `categoryid` = ?'; + + try { + $stmt = \OC_DB::prepare($sql); + $result = $stmt->execute([$tagId]); + if ($result === null) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); + return false; + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + + if (!is_null($result)) { + while ($row = $result->fetchRow()) { + $ids[] = (int)$row['objid']; + } + } + + return $ids; + } + + /** + * Checks whether a tag is saved for the given user, + * disregarding the ones shared with him or her. + * + * @param string $name The tag name to check for. + * @param string $user The user whose tags are to be checked. + * @return bool + */ + public function userHasTag($name, $user) { + $key = $this->array_searchi($name, $this->getTagsForUser($user)); + return ($key !== false) ? $this->tags[$key]->getId() : false; + } + + /** + * Checks whether a tag is saved for or shared with the current user. + * + * @param string $name The tag name to check for. + * @return bool + */ + public function hasTag($name) { + return $this->getTagId($name) !== false; + } + + /** + * Add a new tag. + * + * @param string $name A string with a name of the tag + * @return false|int the id of the added tag or false on error. + */ + public function add($name) { + $name = trim($name); + + if ($name === '') { + \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG); + return false; + } + if ($this->userHasTag($name, $this->user)) { + \OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG); + return false; + } + try { + $tag = new Tag($this->user, $this->type, $name); + $tag = $this->mapper->insert($tag); + $this->tags[] = $tag; + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + \OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG); + return $tag->getId(); + } + + /** + * Rename tag. + * + * @param string|integer $from The name or ID of the existing tag + * @param string $to The new name of the tag. + * @return bool + */ + public function rename($from, $to) { + $from = trim($from); + $to = trim($to); + + if ($to === '' || $from === '') { + \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG); + return false; + } + + if (is_numeric($from)) { + $key = $this->getTagById($from); + } else { + $key = $this->getTagByName($from); + } + if ($key === false) { + \OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG); + return false; + } + $tag = $this->tags[$key]; + + if ($this->userHasTag($to, $tag->getOwner())) { + \OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG); + return false; + } + + try { + $tag->setName($to); + $this->tags[$key] = $this->mapper->update($tag); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + return true; + } + + /** + * Add a list of new tags. + * + * @param string[] $names A string with a name or an array of strings containing + * the name(s) of the tag(s) to add. + * @param bool $sync When true, save the tags + * @param int|null $id int Optional object id to add to this|these tag(s) + * @return bool Returns false on error. + */ + public function addMultiple($names, $sync=false, $id = null) { + if (!is_array($names)) { + $names = [$names]; + } + $names = array_map('trim', $names); + array_filter($names); + + $newones = []; + foreach ($names as $name) { + if (!$this->hasTag($name) && $name !== '') { + $newones[] = new Tag($this->user, $this->type, $name); + } + if (!is_null($id)) { + // Insert $objectid, $categoryid pairs if not exist. + self::$relations[] = ['objid' => $id, 'tag' => $name]; + } + } + $this->tags = array_merge($this->tags, $newones); + if ($sync === true) { + $this->save(); + } + + return true; + } + + /** + * Save the list of tags and their object relations + */ + protected function save() { + if (is_array($this->tags)) { + foreach ($this->tags as $tag) { + try { + if (!$this->mapper->tagExists($tag)) { + $this->mapper->insert($tag); + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + } + + // reload tags to get the proper ids. + $this->tags = $this->mapper->loadTags($this->owners, $this->type); + \OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true), + ILogger::DEBUG); + // Loop through temporarily cached objectid/tagname pairs + // and save relations. + $tags = $this->tags; + // For some reason this is needed or array_search(i) will return 0..? + ksort($tags); + $dbConnection = \OC::$server->getDatabaseConnection(); + foreach (self::$relations as $relation) { + $tagId = $this->getTagId($relation['tag']); + \OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG); + if ($tagId) { + try { + $dbConnection->insertIfNotExist(self::RELATION_TABLE, + [ + 'objid' => $relation['objid'], + 'categoryid' => $tagId, + 'type' => $this->type, + ]); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + } + } + self::$relations = []; // reset + } else { + \OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! ' + . print_r($this->tags, true), ILogger::ERROR); + } + } + + /** + * Delete tags and tag/object relations for a user. + * + * For hooking up on post_deleteUser + * + * @param array $arguments + */ + public static function post_deleteUser($arguments) { + // Find all objectid/tagId pairs. + $result = null; + try { + $stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ?'); + $result = $stmt->execute([$arguments['uid']]); + if ($result === null) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + + if (!is_null($result)) { + try { + $stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `categoryid` = ?'); + while ($row = $result->fetchRow()) { + try { + $stmt->execute([$row['id']]); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + } + try { + $stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ?'); + $result = $stmt->execute([$arguments['uid']]); + if ($result === null) { + \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + } + } + + /** + * Delete tag/object relations from the db + * + * @param array $ids The ids of the objects + * @return boolean Returns false on error. + */ + public function purgeObjects(array $ids) { + if (count($ids) === 0) { + // job done ;) + return true; + } + $updates = $ids; + try { + $query = 'DELETE FROM `' . self::RELATION_TABLE . '` '; + $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids)-1) . '?) '; + $query .= 'AND `type`= ?'; + $updates[] = $this->type; + $stmt = \OC_DB::prepare($query); + $result = $stmt->execute($updates); + if ($result === null) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); + return false; + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + return true; + } + + /** + * Get favorites for an object type + * + * @return array|false An array of object ids. + */ + public function getFavorites() { + if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) { + return []; + } + + try { + return $this->getIdsForTag(ITags::TAG_FAVORITE); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return []; + } + } + + /** + * Add an object to favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function addToFavorites($objid) { + if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) { + $this->add(ITags::TAG_FAVORITE); + } + return $this->tagAs($objid, ITags::TAG_FAVORITE); + } + + /** + * Remove an object from favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function removeFromFavorites($objid) { + return $this->unTag($objid, ITags::TAG_FAVORITE); + } + + /** + * Creates a tag/object relation. + * + * @param int $objid The id of the object + * @param string $tag The id or name of the tag + * @return boolean Returns false on error. + */ + public function tagAs($objid, $tag) { + if (is_string($tag) && !is_numeric($tag)) { + $tag = trim($tag); + if ($tag === '') { + \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG); + return false; + } + if (!$this->hasTag($tag)) { + $this->add($tag); + } + $tagId = $this->getTagId($tag); + } else { + $tagId = $tag; + } + try { + \OC::$server->getDatabaseConnection()->insertIfNotExist(self::RELATION_TABLE, + [ + 'objid' => $objid, + 'categoryid' => $tagId, + 'type' => $this->type, + ]); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + return true; + } + + /** + * Delete single tag/object relation from the db + * + * @param int $objid The id of the object + * @param string $tag The id or name of the tag + * @return boolean + */ + public function unTag($objid, $tag) { + if (is_string($tag) && !is_numeric($tag)) { + $tag = trim($tag); + if ($tag === '') { + \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG); + return false; + } + $tagId = $this->getTagId($tag); + } else { + $tagId = $tag; + } + + try { + $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?'; + $stmt = \OC_DB::prepare($sql); + $stmt->execute([$objid, $tagId, $this->type]); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + return true; + } + + /** + * Delete tags from the database. + * + * @param string[]|integer[] $names An array of tags (names or IDs) to delete + * @return bool Returns false on error + */ + public function delete($names) { + if (!is_array($names)) { + $names = [$names]; + } + + $names = array_map('trim', $names); + array_filter($names); + + \OCP\Util::writeLog('core', __METHOD__ . ', before: ' + . print_r($this->tags, true), ILogger::DEBUG); + foreach ($names as $name) { + $id = null; + + if (is_numeric($name)) { + $key = $this->getTagById($name); + } else { + $key = $this->getTagByName($name); + } + if ($key !== false) { + $tag = $this->tags[$key]; + $id = $tag->getId(); + unset($this->tags[$key]); + $this->mapper->delete($tag); + } else { + \OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name + . ': not found.', ILogger::ERROR); + } + if (!is_null($id) && $id !== false) { + try { + $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `categoryid` = ?'; + $stmt = \OC_DB::prepare($sql); + $result = $stmt->execute([$id]); + if ($result === null) { + \OCP\Util::writeLog('core', + __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), + ILogger::ERROR); + return false; + } + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => __METHOD__, + 'level' => ILogger::ERROR, + 'app' => 'core', + ]); + return false; + } + } + } + return true; + } + + // case-insensitive array_search + protected function array_searchi($needle, $haystack, $mem='getName') { + if (!is_array($haystack)) { + return false; + } + return array_search(strtolower($needle), array_map( + function ($tag) use ($mem) { + return strtolower(call_user_func([$tag, $mem])); + }, $haystack) + ); + } + + /** + * Get a tag's ID. + * + * @param string $name The tag name to look for. + * @return string|bool The tag's id or false if no matching tag is found. + */ + private function getTagId($name) { + $key = $this->array_searchi($name, $this->tags); + if ($key !== false) { + return $this->tags[$key]->getId(); + } + return false; + } + + /** + * Get a tag by its name. + * + * @param string $name The tag name. + * @return integer|bool The tag object's offset within the $this->tags + * array or false if it doesn't exist. + */ + private function getTagByName($name) { + return $this->array_searchi($name, $this->tags, 'getName'); + } + + /** + * Get a tag by its ID. + * + * @param string $id The tag ID to look for. + * @return integer|bool The tag object's offset within the $this->tags + * array or false if it doesn't exist. + */ + private function getTagById($id) { + return $this->array_searchi($id, $this->tags, 'getId'); + } + + /** + * Returns an array mapping a given tag's properties to its values: + * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype'] + * + * @param Tag $tag The tag that is going to be mapped + * @return array + */ + private function tagMap(Tag $tag) { + return [ + 'id' => $tag->getId(), + 'name' => $tag->getName(), + 'owner' => $tag->getOwner(), + 'type' => $tag->getType() + ]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/TempManager.php b/docker/overlays/nextcloud/html/lib/private/TempManager.php new file mode 100644 index 0000000..e499556 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/TempManager.php @@ -0,0 +1,281 @@ + + * @author Joas Schilling + * @author Lars + * @author Lukas Reschke + * @author Martin Mattel + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Stefan Weil + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\IConfig; +use OCP\ITempManager; +use Psr\Log\LoggerInterface; + +class TempManager implements ITempManager { + /** @var string[] Current temporary files and folders, used for cleanup */ + protected $current = []; + /** @var string i.e. /tmp on linux systems */ + protected $tmpBaseDir; + /** @var LoggerInterface */ + protected $log; + /** @var IConfig */ + protected $config; + /** @var IniGetWrapper */ + protected $iniGetWrapper; + + /** Prefix */ + public const TMP_PREFIX = 'oc_tmp_'; + + public function __construct(LoggerInterface $logger, IConfig $config, IniGetWrapper $iniGetWrapper) { + $this->log = $logger; + $this->config = $config; + $this->iniGetWrapper = $iniGetWrapper; + $this->tmpBaseDir = $this->getTempBaseDir(); + } + + /** + * Builds the filename with suffix and removes potential dangerous characters + * such as directory separators. + * + * @param string $absolutePath Absolute path to the file / folder + * @param string $postFix Postfix appended to the temporary file name, may be user controlled + * @return string + */ + private function buildFileNameWithSuffix($absolutePath, $postFix = '') { + if ($postFix !== '') { + $postFix = '.' . ltrim($postFix, '.'); + $postFix = str_replace(['\\', '/'], '', $postFix); + $absolutePath .= '-'; + } + + return $absolutePath . $postFix; + } + + /** + * Create a temporary file and return the path + * + * @param string $postFix Postfix appended to the temporary file name + * @return string + */ + public function getTemporaryFile($postFix = '') { + if (is_writable($this->tmpBaseDir)) { + // To create an unique file and prevent the risk of race conditions + // or duplicated temporary files by other means such as collisions + // we need to create the file using `tempnam` and append a possible + // postfix to it later + $file = tempnam($this->tmpBaseDir, self::TMP_PREFIX); + $this->current[] = $file; + + // If a postfix got specified sanitize it and create a postfixed + // temporary file + if ($postFix !== '') { + $fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix); + touch($fileNameWithPostfix); + chmod($fileNameWithPostfix, 0600); + $this->current[] = $fileNameWithPostfix; + return $fileNameWithPostfix; + } + + return $file; + } else { + $this->log->warning( + 'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions', + [ + 'dir' => $this->tmpBaseDir, + ] + ); + return false; + } + } + + /** + * Create a temporary folder and return the path + * + * @param string $postFix Postfix appended to the temporary folder name + * @return string + */ + public function getTemporaryFolder($postFix = '') { + if (is_writable($this->tmpBaseDir)) { + // To create an unique directory and prevent the risk of race conditions + // or duplicated temporary files by other means such as collisions + // we need to create the file using `tempnam` and append a possible + // postfix to it later + $uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX); + $this->current[] = $uniqueFileName; + + // Build a name without postfix + $path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix); + mkdir($path, 0700); + $this->current[] = $path; + + return $path . '/'; + } else { + $this->log->warning( + 'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions', + [ + 'dir' => $this->tmpBaseDir, + ] + ); + return false; + } + } + + /** + * Remove the temporary files and folders generated during this request + */ + public function clean() { + $this->cleanFiles($this->current); + } + + /** + * @param string[] $files + */ + protected function cleanFiles($files) { + foreach ($files as $file) { + if (file_exists($file)) { + try { + \OC_Helper::rmdirr($file); + } catch (\UnexpectedValueException $ex) { + $this->log->warning( + "Error deleting temporary file/folder: {file} - Reason: {error}", + [ + 'file' => $file, + 'error' => $ex->getMessage(), + ] + ); + } + } + } + } + + /** + * Remove old temporary files and folders that were failed to be cleaned + */ + public function cleanOld() { + $this->cleanFiles($this->getOldFiles()); + } + + /** + * Get all temporary files and folders generated by oc older than an hour + * + * @return string[] + */ + protected function getOldFiles() { + $cutOfTime = time() - 3600; + $files = []; + $dh = opendir($this->tmpBaseDir); + if ($dh) { + while (($file = readdir($dh)) !== false) { + if (substr($file, 0, 7) === self::TMP_PREFIX) { + $path = $this->tmpBaseDir . '/' . $file; + $mtime = filemtime($path); + if ($mtime < $cutOfTime) { + $files[] = $path; + } + } + } + } + return $files; + } + + /** + * Get the temporary base directory configured on the server + * + * @return string Path to the temporary directory or null + * @throws \UnexpectedValueException + */ + public function getTempBaseDir() { + if ($this->tmpBaseDir) { + return $this->tmpBaseDir; + } + + $directories = []; + if ($temp = $this->config->getSystemValue('tempdirectory', null)) { + $directories[] = $temp; + } + if ($temp = $this->iniGetWrapper->get('upload_tmp_dir')) { + $directories[] = $temp; + } + if ($temp = getenv('TMP')) { + $directories[] = $temp; + } + if ($temp = getenv('TEMP')) { + $directories[] = $temp; + } + if ($temp = getenv('TMPDIR')) { + $directories[] = $temp; + } + if ($temp = sys_get_temp_dir()) { + $directories[] = $temp; + } + + foreach ($directories as $dir) { + if ($this->checkTemporaryDirectory($dir)) { + return $dir; + } + } + + $temp = tempnam(dirname(__FILE__), ''); + if (file_exists($temp)) { + unlink($temp); + return dirname($temp); + } + throw new \UnexpectedValueException('Unable to detect system temporary directory'); + } + + /** + * Check if a temporary directory is ready for use + * + * @param mixed $directory + * @return bool + */ + private function checkTemporaryDirectory($directory) { + // suppress any possible errors caused by is_writable + // checks missing or invalid path or characters, wrong permissions etc + try { + if (is_writable($directory)) { + return true; + } + } catch (\Exception $e) { + } + $this->log->warning('Temporary directory {dir} is not present or writable', + ['dir' => $directory] + ); + return false; + } + + /** + * Override the temporary base directory + * + * @param string $directory + */ + public function overrideTempBaseDir($directory) { + $this->tmpBaseDir = $directory; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/Base.php b/docker/overlays/nextcloud/html/lib/private/Template/Base.php new file mode 100644 index 0000000..04d03b6 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/Base.php @@ -0,0 +1,189 @@ + + * @author Björn Schießle + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +use OCP\Defaults; + +class Base { + private $template; // The template + private $vars; // Vars + + /** @var \OCP\IL10N */ + private $l10n; + + /** @var Defaults */ + private $theme; + + /** + * @param string $template + * @param string $requestToken + * @param \OCP\IL10N $l10n + * @param Defaults $theme + */ + public function __construct($template, $requestToken, $l10n, $theme) { + $this->vars = []; + $this->vars['requesttoken'] = $requestToken; + $this->l10n = $l10n; + $this->template = $template; + $this->theme = $theme; + } + + /** + * @param string $serverRoot + * @param string|false $app_dir + * @param string $theme + * @param string $app + * @return string[] + */ + protected function getAppTemplateDirs($theme, $app, $serverRoot, $app_dir) { + // Check if the app is in the app folder or in the root + if (file_exists($app_dir.'/templates/')) { + return [ + $serverRoot.'/themes/'.$theme.'/apps/'.$app.'/templates/', + $app_dir.'/templates/', + ]; + } + return [ + $serverRoot.'/themes/'.$theme.'/'.$app.'/templates/', + $serverRoot.'/'.$app.'/templates/', + ]; + } + + /** + * @param string $serverRoot + * @param string $theme + * @return string[] + */ + protected function getCoreTemplateDirs($theme, $serverRoot) { + return [ + $serverRoot.'/themes/'.$theme.'/core/templates/', + $serverRoot.'/core/templates/', + ]; + } + + /** + * Assign variables + * @param string $key key + * @param array|bool|integer|string $value value + * @return bool + * + * This function assigns a variable. It can be accessed via $_[$key] in + * the template. + * + * If the key existed before, it will be overwritten + */ + public function assign($key, $value) { + $this->vars[$key] = $value; + return true; + } + + /** + * Appends a variable + * @param string $key key + * @param mixed $value value + * + * This function assigns a variable in an array context. If the key already + * exists, the value will be appended. It can be accessed via + * $_[$key][$position] in the template. + */ + public function append($key, $value) { + if (array_key_exists($key, $this->vars)) { + $this->vars[$key][] = $value; + } else { + $this->vars[$key] = [ $value ]; + } + } + + /** + * Prints the proceeded template + * @return bool + * + * This function proceeds the template and prints its output. + */ + public function printPage() { + $data = $this->fetchPage(); + if ($data === false) { + return false; + } else { + print $data; + return true; + } + } + + /** + * Process the template + * + * @param array|null $additionalParams + * @return string This function processes the template. + * + * This function processes the template. + */ + public function fetchPage($additionalParams = null) { + return $this->load($this->template, $additionalParams); + } + + /** + * doing the actual work + * + * @param string $file + * @param array|null $additionalParams + * @return string content + * + * Includes the template file, fetches its output + */ + protected function load($file, $additionalParams = null) { + // Register the variables + $_ = $this->vars; + $l = $this->l10n; + $theme = $this->theme; + + if (!is_null($additionalParams)) { + $_ = array_merge($additionalParams, $this->vars); + foreach ($_ as $var => $value) { + ${$var} = $value; + } + } + + // Include + ob_start(); + try { + include $file; + $data = ob_get_contents(); + } catch (\Exception $e) { + @ob_end_clean(); + throw $e; + } + @ob_end_clean(); + + // Return data + return $data; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/CSSResourceLocator.php b/docker/overlays/nextcloud/html/lib/private/Template/CSSResourceLocator.php new file mode 100644 index 0000000..b5c16d1 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/CSSResourceLocator.php @@ -0,0 +1,151 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Kyle Fazzari + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author tux-rampage + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +use OCP\ILogger; + +class CSSResourceLocator extends ResourceLocator { + + /** @var SCSSCacher */ + protected $scssCacher; + + /** + * @param ILogger $logger + * @param string $theme + * @param array $core_map + * @param array $party_map + * @param SCSSCacher $scssCacher + */ + public function __construct(ILogger $logger, $theme, $core_map, $party_map, $scssCacher) { + $this->scssCacher = $scssCacher; + + parent::__construct($logger, $theme, $core_map, $party_map); + } + + /** + * @param string $style + */ + public function doFind($style) { + $app = substr($style, 0, strpos($style, '/')); + if (strpos($style, '3rdparty') === 0 + && $this->appendIfExist($this->thirdpartyroot, $style.'.css') + || $this->cacheAndAppendScssIfExist($this->serverroot, $style.'.scss', $app) + || $this->cacheAndAppendScssIfExist($this->serverroot, 'core/'.$style.'.scss') + || $this->appendIfExist($this->serverroot, $style.'.css') + || $this->appendIfExist($this->serverroot, 'core/'.$style.'.css') + ) { + return; + } + $style = substr($style, strpos($style, '/')+1); + $app_path = \OC_App::getAppPath($app); + $app_url = \OC_App::getAppWebPath($app); + + if ($app_path === false && $app_url === false) { + $this->logger->error('Could not find resource {resource} to load', [ + 'resource' => $app . '/' . $style . '.css', + 'app' => 'cssresourceloader', + ]); + return; + } + + // Account for the possibility of having symlinks in app path. Doing + // this here instead of above as an empty argument to realpath gets + // turned into cwd. + $app_path = realpath($app_path); + + if (!$this->cacheAndAppendScssIfExist($app_path, $style.'.scss', $app)) { + $this->append($app_path, $style.'.css', $app_url); + } + } + + /** + * @param string $style + */ + public function doFindTheme($style) { + $theme_dir = 'themes/'.$this->theme.'/'; + $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$style.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.$style.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$style.'.css'); + } + + /** + * cache and append the scss $file if exist at $root + * + * @param string $root path to check + * @param string $file the filename + * @return bool True if the resource was found and cached, false otherwise + */ + protected function cacheAndAppendScssIfExist($root, $file, $app = 'core') { + if (is_file($root.'/'.$file)) { + if ($this->scssCacher !== null) { + if ($this->scssCacher->process($root, $file, $app)) { + $this->append($this->serverroot, $this->scssCacher->getCachedSCSS($app, $file), \OC::$WEBROOT, true, true); + return true; + } else { + $this->logger->warning('Failed to compile and/or save '.$root.'/'.$file, ['app' => 'core']); + return false; + } + } else { + return true; + } + } + return false; + } + + public function append($root, $file, $webRoot = null, $throw = true, $scss = false) { + if (!$scss) { + parent::append($root, $file, $webRoot, $throw); + } else { + if (!$webRoot) { + $webRoot = $this->findWebRoot($root); + + if ($webRoot === null) { + $webRoot = ''; + $this->logger->error('ResourceLocator can not find a web root (root: {root}, file: {file}, webRoot: {webRoot}, throw: {throw})', [ + 'app' => 'lib', + 'root' => $root, + 'file' => $file, + 'webRoot' => $webRoot, + 'throw' => $throw ? 'true' : 'false' + ]); + + if ($throw && $root === '/') { + throw new ResourceNotFoundException($file, $webRoot); + } + } + } + + $this->resources[] = [$webRoot ?: \OC::$WEBROOT, $webRoot, $file]; + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/IconsCacher.php b/docker/overlays/nextcloud/html/lib/private/Template/IconsCacher.php new file mode 100644 index 0000000..f9497c9 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/IconsCacher.php @@ -0,0 +1,267 @@ + + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Julius Härtl + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Template; + +use OC\Files\AppData\Factory; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\ILogger; +use OCP\IURLGenerator; + +class IconsCacher { + + /** @var ILogger */ + protected $logger; + + /** @var IAppData */ + protected $appData; + + /** @var ISimpleFolder */ + private $folder; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var ITimeFactory */ + protected $timeFactory; + + /** @var string */ + private $iconVarRE = '/--(icon-[a-zA-Z0-9-]+):\s?url\(["\']?([a-zA-Z0-9-_\~\/\.\?\&\=\:\;\+\,]+)[^;]+;/m'; + + /** @var string */ + private $fileName = 'icons-vars.css'; + + private $iconList = 'icons-list.template'; + + private $cachedCss; + private $cachedList; + + /** + * @param ILogger $logger + * @param Factory $appDataFactory + * @param IURLGenerator $urlGenerator + * @param ITimeFactory $timeFactory + * @throws \OCP\Files\NotPermittedException + */ + public function __construct(ILogger $logger, + Factory $appDataFactory, + IURLGenerator $urlGenerator, + ITimeFactory $timeFactory) { + $this->logger = $logger; + $this->appData = $appDataFactory->get('css'); + $this->urlGenerator = $urlGenerator; + $this->timeFactory = $timeFactory; + + try { + $this->folder = $this->appData->getFolder('icons'); + } catch (NotFoundException $e) { + $this->folder = $this->appData->newFolder('icons'); + } + } + + private function getIconsFromCss(string $css): array { + preg_match_all($this->iconVarRE, $css, $matches, PREG_SET_ORDER); + $icons = []; + foreach ($matches as $icon) { + $icons[$icon[1]] = $icon[2]; + } + + return $icons; + } + + /** + * @param string $css + * @return string + * @throws NotFoundException + * @throws \OCP\Files\NotPermittedException + */ + public function setIconsCss(string $css): string { + $cachedFile = $this->getCachedList(); + if (!$cachedFile) { + $currentData = ''; + $cachedFile = $this->folder->newFile($this->iconList); + } else { + $currentData = $cachedFile->getContent(); + } + + $cachedVarsCssFile = $this->getCachedCSS(); + if (!$cachedVarsCssFile) { + $cachedVarsCssFile = $this->folder->newFile($this->fileName); + } + + $icons = $this->getIconsFromCss($currentData . $css); + + $data = ''; + $list = ''; + foreach ($icons as $icon => $url) { + $list .= "--$icon: url('$url');"; + list($location,$color) = $this->parseUrl($url); + $svg = false; + if ($location !== '' && \file_exists($location)) { + $svg = \file_get_contents($location); + } + if ($svg === false) { + $this->logger->debug('Failed to get icon file ' . $location); + $data .= "--$icon: url('$url');"; + continue; + } + $encode = base64_encode($this->colorizeSvg($svg, $color)); + $data .= '--' . $icon . ': url(data:image/svg+xml;base64,' . $encode . ');'; + } + + if (\strlen($data) > 0 && \strlen($list) > 0) { + $data = ":root {\n$data\n}"; + $cachedVarsCssFile->putContent($data); + $list = ":root {\n$list\n}"; + $cachedFile->putContent($list); + $this->cachedList = null; + $this->cachedCss = null; + } + + return preg_replace($this->iconVarRE, '', $css); + } + + /** + * @param $url + * @return array + */ + private function parseUrl($url): array { + $location = ''; + $color = ''; + $base = $this->getRoutePrefix() . '/svg/'; + $cleanUrl = \substr($url, \strlen($base)); + if (\strpos($url, $base . 'core') === 0) { + $cleanUrl = \substr($cleanUrl, \strlen('core')); + if (\preg_match('/\/([a-zA-Z0-9-_\~\/\.\=\:\;\+\,]+)\?color=([0-9a-fA-F]{3,6})/', $cleanUrl, $matches)) { + list(,$cleanUrl,$color) = $matches; + $location = \OC::$SERVERROOT . '/core/img/' . $cleanUrl . '.svg'; + } + } elseif (\strpos($url, $base) === 0) { + if (\preg_match('/([A-z0-9\_\-]+)\/([a-zA-Z0-9-_\~\/\.\=\:\;\+\,]+)\?color=([0-9a-fA-F]{3,6})/', $cleanUrl, $matches)) { + list(,$app,$cleanUrl, $color) = $matches; + $location = \OC_App::getAppPath($app) . '/img/' . $cleanUrl . '.svg'; + if ($app === 'settings') { + $location = \OC::$SERVERROOT . '/settings/img/' . $cleanUrl . '.svg'; + } + } + } + return [ + $location, + $color + ]; + } + + /** + * @param $svg + * @param $color + * @return string + */ + public function colorizeSvg($svg, $color): string { + if (!preg_match('/^[0-9a-f]{3,6}$/i', $color)) { + // Prevent not-sane colors from being written into the SVG + $color = '000'; + } + + // add fill (fill is not present on black elements) + $fillRe = '/<((circle|rect|path)((?!fill)[a-z0-9 =".\-#():;,])+)\/>/mi'; + $svg = preg_replace($fillRe, '<$1 fill="#' . $color . '"/>', $svg); + + // replace any fill or stroke colors + $svg = preg_replace('/stroke="#([a-z0-9]{3,6})"/mi', 'stroke="#' . $color . '"', $svg); + $svg = preg_replace('/fill="#([a-z0-9]{3,6})"/mi', 'fill="#' . $color . '"', $svg); + return $svg; + } + + private function getRoutePrefix() { + $frontControllerActive = (\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); + $prefix = \OC::$WEBROOT . '/index.php'; + if ($frontControllerActive) { + $prefix = \OC::$WEBROOT; + } + return $prefix; + } + + /** + * Get icons css file + * @return ISimpleFile|boolean + */ + public function getCachedCSS() { + try { + if (!$this->cachedCss) { + $this->cachedCss = $this->folder->getFile($this->fileName); + } + return $this->cachedCss; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * Get icon-vars list template + * @return ISimpleFile|boolean + */ + public function getCachedList() { + try { + if (!$this->cachedList) { + $this->cachedList = $this->folder->getFile($this->iconList); + } + return $this->cachedList; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * Add the icons cache css into the header + */ + public function injectCss() { + $mtime = $this->timeFactory->getTime(); + $file = $this->getCachedList(); + if ($file) { + $mtime = $file->getMTime(); + } + // Only inject once + foreach (\OC_Util::$headers as $header) { + if ( + array_key_exists('attributes', $header) && + array_key_exists('href', $header['attributes']) && + strpos($header['attributes']['href'], $this->fileName) !== false) { + return; + } + } + $linkToCSS = $this->urlGenerator->linkToRoute('core.Css.getCss', ['appName' => 'icons', 'fileName' => $this->fileName, 'v' => $mtime]); + \OC_Util::addHeader('link', ['rel' => 'stylesheet', 'href' => $linkToCSS], null, true); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/JSCombiner.php b/docker/overlays/nextcloud/html/lib/private/Template/JSCombiner.php new file mode 100644 index 0000000..48f6dad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/JSCombiner.php @@ -0,0 +1,262 @@ + + * + * @author Christoph Wurst + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Template; + +use OC\SystemConfig; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\ILogger; +use OCP\IURLGenerator; + +class JSCombiner { + + /** @var IAppData */ + protected $appData; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var ICache */ + protected $depsCache; + + /** @var SystemConfig */ + protected $config; + + /** @var ILogger */ + protected $logger; + + /** @var ICacheFactory */ + private $cacheFactory; + + /** + * @param IAppData $appData + * @param IURLGenerator $urlGenerator + * @param ICacheFactory $cacheFactory + * @param SystemConfig $config + * @param ILogger $logger + */ + public function __construct(IAppData $appData, + IURLGenerator $urlGenerator, + ICacheFactory $cacheFactory, + SystemConfig $config, + ILogger $logger) { + $this->appData = $appData; + $this->urlGenerator = $urlGenerator; + $this->cacheFactory = $cacheFactory; + $this->depsCache = $this->cacheFactory->createDistributed('JS-' . md5($this->urlGenerator->getBaseUrl())); + $this->config = $config; + $this->logger = $logger; + } + + /** + * @param string $root + * @param string $file + * @param string $app + * @return bool + */ + public function process($root, $file, $app) { + if ($this->config->getValue('debug') || !$this->config->getValue('installed')) { + return false; + } + + $path = explode('/', $root . '/' . $file); + + $fileName = array_pop($path); + $path = implode('/', $path); + + try { + $folder = $this->appData->getFolder($app); + } catch (NotFoundException $e) { + // creating css appdata folder + $folder = $this->appData->newFolder($app); + } + + if ($this->isCached($fileName, $folder)) { + return true; + } + return $this->cache($path, $fileName, $folder); + } + + /** + * @param string $fileName + * @param ISimpleFolder $folder + * @return bool + */ + protected function isCached($fileName, ISimpleFolder $folder) { + $fileName = str_replace('.json', '.js', $fileName); + + if (!$folder->fileExists($fileName)) { + return false; + } + + $fileName = $fileName . '.deps'; + try { + $deps = $this->depsCache->get($folder->getName() . '-' . $fileName); + if ($deps === null || $deps === '') { + $depFile = $folder->getFile($fileName); + $deps = $depFile->getContent(); + } + + // check again + if ($deps === null || $deps === '') { + $this->logger->info('JSCombiner: deps file empty: ' . $fileName); + return false; + } + + $deps = json_decode($deps, true); + + if ($deps === null) { + return false; + } + + foreach ($deps as $file=>$mtime) { + if (!file_exists($file) || filemtime($file) > $mtime) { + return false; + } + } + + return true; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * @param string $path + * @param string $fileName + * @param ISimpleFolder $folder + * @return bool + */ + protected function cache($path, $fileName, ISimpleFolder $folder) { + $deps = []; + $fullPath = $path . '/' . $fileName; + $data = json_decode(file_get_contents($fullPath)); + $deps[$fullPath] = filemtime($fullPath); + + $res = ''; + foreach ($data as $file) { + $filePath = $path . '/' . $file; + + if (is_file($filePath)) { + $res .= file_get_contents($filePath); + $res .= PHP_EOL . PHP_EOL; + $deps[$filePath] = filemtime($filePath); + } + } + + $fileName = str_replace('.json', '.js', $fileName); + try { + $cachedfile = $folder->getFile($fileName); + } catch (NotFoundException $e) { + $cachedfile = $folder->newFile($fileName); + } + + $depFileName = $fileName . '.deps'; + try { + $depFile = $folder->getFile($depFileName); + } catch (NotFoundException $e) { + $depFile = $folder->newFile($depFileName); + } + + try { + $gzipFile = $folder->getFile($fileName . '.gzip'); # Safari doesn't like .gz + } catch (NotFoundException $e) { + $gzipFile = $folder->newFile($fileName . '.gzip'); # Safari doesn't like .gz + } + + try { + $cachedfile->putContent($res); + $deps = json_encode($deps); + $depFile->putContent($deps); + $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps); + $gzipFile->putContent(gzencode($res, 9)); + $this->logger->debug('JSCombiner: successfully cached: ' . $fileName); + return true; + } catch (NotPermittedException $e) { + $this->logger->error('JSCombiner: unable to cache: ' . $fileName); + return false; + } + } + + /** + * @param string $appName + * @param string $fileName + * @return string + */ + public function getCachedJS($appName, $fileName) { + $tmpfileLoc = explode('/', $fileName); + $fileName = array_pop($tmpfileLoc); + $fileName = str_replace('.json', '.js', $fileName); + + return substr($this->urlGenerator->linkToRoute('core.Js.getJs', ['fileName' => $fileName, 'appName' => $appName]), strlen(\OC::$WEBROOT) + 1); + } + + /** + * @param string $root + * @param string $file + * @return string[] + */ + public function getContent($root, $file) { + /** @var array $data */ + $data = json_decode(file_get_contents($root . '/' . $file)); + if (!is_array($data)) { + return []; + } + + $path = explode('/', $file); + array_pop($path); + $path = implode('/', $path); + + $result = []; + foreach ($data as $f) { + $result[] = $path . '/' . $f; + } + + return $result; + } + + + /** + * Clear cache with combined javascript files + * + * @throws NotFoundException + */ + public function resetCache() { + $this->cacheFactory->createDistributed('JS-')->clear(); + $appDirectory = $this->appData->getDirectoryListing(); + foreach ($appDirectory as $folder) { + foreach ($folder->getDirectoryListing() as $file) { + $file->delete(); + } + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/JSConfigHelper.php b/docker/overlays/nextcloud/html/lib/private/Template/JSConfigHelper.php new file mode 100644 index 0000000..e18081f --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/JSConfigHelper.php @@ -0,0 +1,321 @@ + + * + * @author Abijeet + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Joas Schilling + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Template; + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\CapabilitiesManager; +use OCP\App\IAppManager; +use OCP\Defaults; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IInitialStateService; +use OCP\IL10N; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\User\Backend\IPasswordConfirmationBackend; + +class JSConfigHelper { + + /** @var IL10N */ + private $l; + + /** @var Defaults */ + private $defaults; + + /** @var IAppManager */ + private $appManager; + + /** @var ISession */ + private $session; + + /** @var IUser|null */ + private $currentUser; + + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IniGetWrapper */ + private $iniWrapper; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var CapabilitiesManager */ + private $capabilitiesManager; + + /** @var IInitialStateService */ + private $initialStateService; + + /** @var array user back-ends excluded from password verification */ + private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; + + /** + * @param IL10N $l + * @param Defaults $defaults + * @param IAppManager $appManager + * @param ISession $session + * @param IUser|null $currentUser + * @param IConfig $config + * @param IGroupManager $groupManager + * @param IniGetWrapper $iniWrapper + * @param IURLGenerator $urlGenerator + * @param CapabilitiesManager $capabilitiesManager + */ + public function __construct(IL10N $l, + Defaults $defaults, + IAppManager $appManager, + ISession $session, + $currentUser, + IConfig $config, + IGroupManager $groupManager, + IniGetWrapper $iniWrapper, + IURLGenerator $urlGenerator, + CapabilitiesManager $capabilitiesManager, + IInitialStateService $initialStateService) { + $this->l = $l; + $this->defaults = $defaults; + $this->appManager = $appManager; + $this->session = $session; + $this->currentUser = $currentUser; + $this->config = $config; + $this->groupManager = $groupManager; + $this->iniWrapper = $iniWrapper; + $this->urlGenerator = $urlGenerator; + $this->capabilitiesManager = $capabilitiesManager; + $this->initialStateService = $initialStateService; + } + + public function getConfig() { + $userBackendAllowsPasswordConfirmation = true; + if ($this->currentUser !== null) { + $uid = $this->currentUser->getUID(); + + $backend = $this->currentUser->getBackend(); + if ($backend instanceof IPasswordConfirmationBackend) { + $userBackendAllowsPasswordConfirmation = $backend->canConfirmPassword($uid); + } elseif (isset($this->excludedUserBackEnds[$this->currentUser->getBackendClassName()])) { + $userBackendAllowsPasswordConfirmation = false; + } + } else { + $uid = null; + } + + // Get the config + $apps_paths = []; + + if ($this->currentUser === null) { + $apps = $this->appManager->getInstalledApps(); + } else { + $apps = $this->appManager->getEnabledAppsForUser($this->currentUser); + } + + foreach ($apps as $app) { + $apps_paths[$app] = \OC_App::getAppWebPath($app); + } + + + $enableLinkPasswordByDefault = $this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no'); + $enableLinkPasswordByDefault = $enableLinkPasswordByDefault === 'yes'; + $defaultExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + $defaultExpireDate = $enforceDefaultExpireDate = null; + if ($defaultExpireDateEnabled) { + $defaultExpireDate = (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + $enforceDefaultExpireDate = $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + $outgoingServer2serverShareEnabled = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; + + $defaultInternalExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes'; + $defaultInternalExpireDate = $defaultInternalExpireDateEnforced = null; + if ($defaultInternalExpireDateEnabled) { + $defaultInternalExpireDate = (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'); + $defaultInternalExpireDateEnforced = $this->config->getAppValue('core', 'shareapi_internal_enforce_expire_date', 'no') === 'yes'; + } + + $countOfDataLocation = 0; + $dataLocation = str_replace(\OC::$SERVERROOT . '/', '', $this->config->getSystemValue('datadirectory', ''), $countOfDataLocation); + if ($countOfDataLocation !== 1 || !$this->groupManager->isAdmin($uid)) { + $dataLocation = false; + } + + if ($this->currentUser instanceof IUser) { + $lastConfirmTimestamp = $this->session->get('last-password-confirm'); + if (!is_int($lastConfirmTimestamp)) { + $lastConfirmTimestamp = 0; + } + } else { + $lastConfirmTimestamp = 0; + } + + $capabilities = $this->capabilitiesManager->getCapabilities(); + + $config = [ + 'session_lifetime' => min($this->config->getSystemValue('session_lifetime', $this->iniWrapper->getNumeric('session.gc_maxlifetime')), $this->iniWrapper->getNumeric('session.gc_maxlifetime')), + 'session_keepalive' => $this->config->getSystemValue('session_keepalive', true), + 'auto_logout' => $this->config->getSystemValue('auto_logout', false), + 'version' => implode('.', \OCP\Util::getVersion()), + 'versionstring' => \OC_Util::getVersionString(), + 'enable_avatars' => true, // here for legacy reasons - to not crash existing code that relies on this value + 'lost_password_link' => $this->config->getSystemValue('lost_password_link', null), + 'modRewriteWorking' => $this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true', + 'sharing.maxAutocompleteResults' => (int)$this->config->getSystemValue('sharing.maxAutocompleteResults', 0), + 'sharing.minSearchStringLength' => (int)$this->config->getSystemValue('sharing.minSearchStringLength', 0), + 'blacklist_files_regex' => \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX, + ]; + + $array = [ + "_oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false', + "_oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false', + "backendAllowsPasswordConfirmation" => $userBackendAllowsPasswordConfirmation ? 'true' : 'false', + "oc_dataURL" => is_string($dataLocation) ? "\"" . $dataLocation . "\"" : 'false', + "_oc_webroot" => "\"" . \OC::$WEBROOT . "\"", + "_oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution + "datepickerFormatDate" => json_encode($this->l->l('jsdate', null)), + 'nc_lastLogin' => $lastConfirmTimestamp, + 'nc_pageLoad' => time(), + "dayNames" => json_encode([ + (string)$this->l->t('Sunday'), + (string)$this->l->t('Monday'), + (string)$this->l->t('Tuesday'), + (string)$this->l->t('Wednesday'), + (string)$this->l->t('Thursday'), + (string)$this->l->t('Friday'), + (string)$this->l->t('Saturday') + ]), + "dayNamesShort" => json_encode([ + (string)$this->l->t('Sun.'), + (string)$this->l->t('Mon.'), + (string)$this->l->t('Tue.'), + (string)$this->l->t('Wed.'), + (string)$this->l->t('Thu.'), + (string)$this->l->t('Fri.'), + (string)$this->l->t('Sat.') + ]), + "dayNamesMin" => json_encode([ + (string)$this->l->t('Su'), + (string)$this->l->t('Mo'), + (string)$this->l->t('Tu'), + (string)$this->l->t('We'), + (string)$this->l->t('Th'), + (string)$this->l->t('Fr'), + (string)$this->l->t('Sa') + ]), + "monthNames" => json_encode([ + (string)$this->l->t('January'), + (string)$this->l->t('February'), + (string)$this->l->t('March'), + (string)$this->l->t('April'), + (string)$this->l->t('May'), + (string)$this->l->t('June'), + (string)$this->l->t('July'), + (string)$this->l->t('August'), + (string)$this->l->t('September'), + (string)$this->l->t('October'), + (string)$this->l->t('November'), + (string)$this->l->t('December') + ]), + "monthNamesShort" => json_encode([ + (string)$this->l->t('Jan.'), + (string)$this->l->t('Feb.'), + (string)$this->l->t('Mar.'), + (string)$this->l->t('Apr.'), + (string)$this->l->t('May.'), + (string)$this->l->t('Jun.'), + (string)$this->l->t('Jul.'), + (string)$this->l->t('Aug.'), + (string)$this->l->t('Sep.'), + (string)$this->l->t('Oct.'), + (string)$this->l->t('Nov.'), + (string)$this->l->t('Dec.') + ]), + "firstDay" => json_encode($this->l->l('firstday', null)), + "_oc_config" => json_encode($config), + "oc_appconfig" => json_encode([ + 'core' => [ + 'defaultExpireDateEnabled' => $defaultExpireDateEnabled, + 'defaultExpireDate' => $defaultExpireDate, + 'defaultExpireDateEnforced' => $enforceDefaultExpireDate, + 'enforcePasswordForPublicLink' => \OCP\Util::isPublicLinkPasswordRequired(), + 'enableLinkPasswordByDefault' => $enableLinkPasswordByDefault, + 'sharingDisabledForUser' => \OCP\Util::isSharingDisabledForUser(), + 'resharingAllowed' => \OC\Share\Share::isResharingAllowed(), + 'remoteShareAllowed' => $outgoingServer2serverShareEnabled, + 'federatedCloudShareDoc' => $this->urlGenerator->linkToDocs('user-sharing-federated'), + 'allowGroupSharing' => \OC::$server->getShareManager()->allowGroupSharing(), + 'defaultInternalExpireDateEnabled' => $defaultInternalExpireDateEnabled, + 'defaultInternalExpireDate' => $defaultInternalExpireDate, + 'defaultInternalExpireDateEnforced' => $defaultInternalExpireDateEnforced, + ] + ]), + "_theme" => json_encode([ + 'entity' => $this->defaults->getEntity(), + 'name' => $this->defaults->getName(), + 'title' => $this->defaults->getTitle(), + 'baseUrl' => $this->defaults->getBaseUrl(), + 'syncClientUrl' => $this->defaults->getSyncClientUrl(), + 'docBaseUrl' => $this->defaults->getDocBaseUrl(), + 'docPlaceholderUrl' => $this->defaults->buildDocLinkToKey('PLACEHOLDER'), + 'slogan' => $this->defaults->getSlogan(), + 'logoClaim' => '', + 'shortFooter' => $this->defaults->getShortFooter(), + 'longFooter' => $this->defaults->getLongFooter(), + 'folder' => \OC_Util::getTheme(), + ]), + ]; + + if ($this->currentUser !== null) { + $array['oc_userconfig'] = json_encode([ + 'avatar' => [ + 'version' => (int)$this->config->getUserValue($uid, 'avatar', 'version', 0), + 'generated' => $this->config->getUserValue($uid, 'avatar', 'generated', 'true') === 'true', + ] + ]); + } + + $this->initialStateService->provideInitialState('core', 'config', $config); + $this->initialStateService->provideInitialState('core', 'capabilities', $capabilities); + + // Allow hooks to modify the output values + \OC_Hook::emit('\OCP\Config', 'js', ['array' => &$array]); + + $result = ''; + + // Echo it + foreach ($array as $setting => $value) { + $result .= 'var '. $setting . '='. $value . ';' . PHP_EOL; + } + + return $result; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/JSResourceLocator.php b/docker/overlays/nextcloud/html/lib/private/Template/JSResourceLocator.php new file mode 100644 index 0000000..50af5fd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/JSResourceLocator.php @@ -0,0 +1,132 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author Kyle Fazzari + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +class JSResourceLocator extends ResourceLocator { + + /** @var JSCombiner */ + protected $jsCombiner; + + public function __construct(\OCP\ILogger $logger, $theme, array $core_map, array $party_map, JSCombiner $JSCombiner) { + parent::__construct($logger, $theme, $core_map, $party_map); + + $this->jsCombiner = $JSCombiner; + } + + /** + * @param string $script + */ + public function doFind($script) { + $theme_dir = 'themes/'.$this->theme.'/'; + if (strpos($script, '3rdparty') === 0 + && $this->appendIfExist($this->thirdpartyroot, $script.'.js')) { + return; + } + + if (strpos($script, '/l10n/') !== false) { + // For language files we try to load them all, so themes can overwrite + // single l10n strings without having to translate all of them. + $found = 0; + $found += $this->appendIfExist($this->serverroot, 'core/'.$script.'.js'); + $found += $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js'); + $found += $this->appendIfExist($this->serverroot, $script.'.js'); + $found += $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js'); + $found += $this->appendIfExist($this->serverroot, 'apps/'.$script.'.js'); + $found += $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js'); + + if ($found) { + return; + } + } elseif ($this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js') + || $this->appendIfExist($this->serverroot, $script.'.js') + || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, $script.'.json') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js') + || $this->appendIfExist($this->serverroot, 'core/'.$script.'.js') + || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, 'core/'.$script.'.json') + ) { + return; + } + + $app = substr($script, 0, strpos($script, '/')); + $script = substr($script, strpos($script, '/')+1); + $app_path = \OC_App::getAppPath($app); + $app_url = \OC_App::getAppWebPath($app); + + if ($app_path !== false) { + // Account for the possibility of having symlinks in app path. Only + // do this if $app_path is set, because an empty argument to realpath + // gets turned into cwd. + $app_path = realpath($app_path); + } + + // missing translations files fill be ignored + if (strpos($script, 'l10n/') === 0) { + $this->appendIfExist($app_path, $script . '.js', $app_url); + return; + } + + if ($app_path === false && $app_url === false) { + $this->logger->error('Could not find resource {resource} to load', [ + 'resource' => $app . '/' . $script . '.js', + 'app' => 'jsresourceloader', + ]); + return; + } + + if (!$this->cacheAndAppendCombineJsonIfExist($app_path, $script.'.json', $app)) { + $this->append($app_path, $script . '.js', $app_url); + } + } + + /** + * @param string $script + */ + public function doFindTheme($script) { + } + + protected function cacheAndAppendCombineJsonIfExist($root, $file, $app = 'core') { + if (is_file($root.'/'.$file)) { + if ($this->jsCombiner->process($root, $file, $app)) { + $this->append($this->serverroot, $this->jsCombiner->getCachedJS($app, $file), false, false); + } else { + // Add all the files from the json + $files = $this->jsCombiner->getContent($root, $file); + $app_url = \OC_App::getAppWebPath($app); + + foreach ($files as $jsFile) { + $this->append($root, $jsFile, $app_url); + } + } + return true; + } + + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/ResourceLocator.php b/docker/overlays/nextcloud/html/lib/private/Template/ResourceLocator.php new file mode 100644 index 0000000..1d9f60c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/ResourceLocator.php @@ -0,0 +1,202 @@ + + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author tux-rampage + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +abstract class ResourceLocator { + protected $theme; + + protected $mapping; + protected $serverroot; + protected $thirdpartyroot; + protected $webroot; + + protected $resources = []; + + /** @var \OCP\ILogger */ + protected $logger; + + /** + * @param \OCP\ILogger $logger + * @param string $theme + * @param array $core_map + * @param array $party_map + */ + public function __construct(\OCP\ILogger $logger, $theme, $core_map, $party_map) { + $this->logger = $logger; + $this->theme = $theme; + $this->mapping = $core_map + $party_map; + $this->serverroot = key($core_map); + $this->thirdpartyroot = key($party_map); + $this->webroot = $this->mapping[$this->serverroot]; + } + + /** + * @param string $resource + */ + abstract public function doFind($resource); + + /** + * @param string $resource + */ + abstract public function doFindTheme($resource); + + /** + * Finds the resources and adds them to the list + * + * @param array $resources + */ + public function find($resources) { + foreach ($resources as $resource) { + try { + $this->doFind($resource); + } catch (ResourceNotFoundException $e) { + $resourceApp = substr($resource, 0, strpos($resource, '/')); + $this->logger->debug('Could not find resource file "' . $e->getResourcePath() . '"', ['app' => $resourceApp]); + } + } + if (!empty($this->theme)) { + foreach ($resources as $resource) { + try { + $this->doFindTheme($resource); + } catch (ResourceNotFoundException $e) { + $resourceApp = substr($resource, 0, strpos($resource, '/')); + $this->logger->debug('Could not find resource file in theme "' . $e->getResourcePath() . '"', ['app' => $resourceApp]); + } + } + } + } + + /** + * append the $file resource if exist at $root + * + * @param string $root path to check + * @param string $file the filename + * @param string|null $webRoot base for path, default map $root to $webRoot + * @return bool True if the resource was found, false otherwise + */ + protected function appendIfExist($root, $file, $webRoot = null) { + if (is_file($root.'/'.$file)) { + $this->append($root, $file, $webRoot, false); + return true; + } + return false; + } + + /** + * Attempt to find the webRoot + * + * traverse the potential web roots upwards in the path + * + * example: + * - root: /srv/www/apps/myapp + * - available mappings: ['/srv/www'] + * + * First we check if a mapping for /srv/www/apps/myapp is available, + * then /srv/www/apps, /srv/www/apps, /srv/www, ... until we find a + * valid web root + * + * @param string $root + * @return string|null The web root or null on failure + */ + protected function findWebRoot($root) { + $webRoot = null; + $tmpRoot = $root; + + while ($webRoot === null) { + if (isset($this->mapping[$tmpRoot])) { + $webRoot = $this->mapping[$tmpRoot]; + break; + } + + if ($tmpRoot === '/') { + break; + } + + $tmpRoot = dirname($tmpRoot); + } + + if ($webRoot === null) { + $realpath = realpath($root); + + if ($realpath && ($realpath !== $root)) { + return $this->findWebRoot($realpath); + } + } + + return $webRoot; + } + + /** + * append the $file resource at $root + * + * @param string $root path to check + * @param string $file the filename + * @param string|null $webRoot base for path, default map $root to $webRoot + * @param bool $throw Throw an exception, when the route does not exist + * @throws ResourceNotFoundException Only thrown when $throw is true and the resource is missing + */ + protected function append($root, $file, $webRoot = null, $throw = true) { + if (!is_string($root)) { + if ($throw) { + throw new ResourceNotFoundException($file, $webRoot); + } + return; + } + + if (!$webRoot) { + $webRoot = $this->findWebRoot($root); + + if ($webRoot === null) { + $webRoot = ''; + $this->logger->error('ResourceLocator can not find a web root (root: {root}, file: {file}, webRoot: {webRoot}, throw: {throw})', [ + 'app' => 'lib', + 'root' => $root, + 'file' => $file, + 'webRoot' => $webRoot, + 'throw' => $throw ? 'true' : 'false' + ]); + } + } + $this->resources[] = [$root, $webRoot, $file]; + + if ($throw && !is_file($root . '/' . $file)) { + throw new ResourceNotFoundException($file, $webRoot); + } + } + + /** + * Returns the list of all resources that should be loaded + * @return array + */ + public function getResources() { + return $this->resources; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/ResourceNotFoundException.php b/docker/overlays/nextcloud/html/lib/private/Template/ResourceNotFoundException.php new file mode 100644 index 0000000..4de9a6b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/ResourceNotFoundException.php @@ -0,0 +1,46 @@ + + * @author Morris Jobke + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +class ResourceNotFoundException extends \LogicException { + protected $resource; + protected $webPath; + + /** + * @param string $resource + * @param string $webPath + */ + public function __construct($resource, $webPath) { + parent::__construct('Resource not found'); + $this->resource = $resource; + $this->webPath = $webPath; + } + + /** + * @return string + */ + public function getResourcePath() { + return $this->webPath . '/' . $this->resource; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/SCSSCacher.php b/docker/overlays/nextcloud/html/lib/private/Template/SCSSCacher.php new file mode 100644 index 0000000..f196747 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/SCSSCacher.php @@ -0,0 +1,533 @@ + + * @author John Molakvoæ (skjnldsv) + * @author Julius Haertl + * @author Julius Härtl + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Roland Tapken + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Template; + +use OC\AppConfig; +use OC\Files\AppData\Factory; +use OC\Memcache\NullCache; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IMemcache; +use OCP\IURLGenerator; +use ScssPhp\ScssPhp\Compiler; +use ScssPhp\ScssPhp\Exception\ParserException; +use ScssPhp\ScssPhp\Formatter\Crunched; +use ScssPhp\ScssPhp\Formatter\Expanded; + +class SCSSCacher { + + /** @var ILogger */ + protected $logger; + + /** @var IAppData */ + protected $appData; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var IConfig */ + protected $config; + + /** @var \OC_Defaults */ + private $defaults; + + /** @var string */ + protected $serverRoot; + + /** @var ICache */ + protected $depsCache; + + /** @var null|string */ + private $injectedVariables; + + /** @var ICacheFactory */ + private $cacheFactory; + + /** @var IconsCacher */ + private $iconsCacher; + + /** @var ICache */ + private $isCachedCache; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var IMemcache */ + private $lockingCache; + /** @var AppConfig */ + private $appConfig; + + /** + * @param ILogger $logger + * @param Factory $appDataFactory + * @param IURLGenerator $urlGenerator + * @param IConfig $config + * @param \OC_Defaults $defaults + * @param string $serverRoot + * @param ICacheFactory $cacheFactory + * @param IconsCacher $iconsCacher + * @param ITimeFactory $timeFactory + */ + public function __construct(ILogger $logger, + Factory $appDataFactory, + IURLGenerator $urlGenerator, + IConfig $config, + \OC_Defaults $defaults, + $serverRoot, + ICacheFactory $cacheFactory, + IconsCacher $iconsCacher, + ITimeFactory $timeFactory, + AppConfig $appConfig) { + $this->logger = $logger; + $this->appData = $appDataFactory->get('css'); + $this->urlGenerator = $urlGenerator; + $this->config = $config; + $this->defaults = $defaults; + $this->serverRoot = $serverRoot; + $this->cacheFactory = $cacheFactory; + $this->depsCache = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl())); + $this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl())); + $lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl())); + if (!($lockingCache instanceof IMemcache)) { + $lockingCache = new NullCache(); + } + $this->lockingCache = $lockingCache; + $this->iconsCacher = $iconsCacher; + $this->timeFactory = $timeFactory; + $this->appConfig = $appConfig; + } + + /** + * Process the caching process if needed + * + * @param string $root Root path to the nextcloud installation + * @param string $file + * @param string $app The app name + * @return boolean + * @throws NotPermittedException + */ + public function process(string $root, string $file, string $app): bool { + $path = explode('/', $root . '/' . $file); + + $fileNameSCSS = array_pop($path); + $fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app); + + $path = implode('/', $path); + $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT); + + $this->logger->debug('SCSSCacher::process ordinary check follows', ['app' => 'scss_cacher']); + if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) { + // Inject icons vars css if any + return $this->injectCssVariablesIfAny(); + } + + try { + $folder = $this->appData->getFolder($app); + } catch (NotFoundException $e) { + // creating css appdata folder + $folder = $this->appData->newFolder($app); + } + + $lockKey = $webDir . '/' . $fileNameSCSS; + + if (!$this->lockingCache->add($lockKey, 'locked!', 120)) { + $this->logger->debug('SCSSCacher::process could not get lock for ' . $lockKey . ' and will wait 10 seconds for cached file to be available', ['app' => 'scss_cacher']); + $retry = 0; + sleep(1); + while ($retry < 10) { + $this->appConfig->clearCachedConfig(); + $this->logger->debug('SCSSCacher::process check in while loop follows', ['app' => 'scss_cacher']); + if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) { + // Inject icons vars css if any + $this->logger->debug("SCSSCacher::process cached file for app '$app' and file '$fileNameCSS' is now available after $retry s. Moving on...", ['app' => 'scss_cacher']); + return $this->injectCssVariablesIfAny(); + } + sleep(1); + $retry++; + } + $this->logger->debug('SCSSCacher::process Giving up scss caching for ' . $lockKey, ['app' => 'scss_cacher']); + return false; + } + + $this->logger->debug('SCSSCacher::process Lock acquired for ' . $lockKey, ['app' => 'scss_cacher']); + try { + $cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir); + } catch (\Exception $e) { + $this->lockingCache->remove($lockKey); + throw $e; + } + + // Cleaning lock + $this->lockingCache->remove($lockKey); + $this->logger->debug('SCSSCacher::process Lock removed for ' . $lockKey, ['app' => 'scss_cacher']); + + // Inject icons vars css if any + if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) { + $this->iconsCacher->injectCss(); + } + + return $cached; + } + + /** + * @param $appName + * @param $fileName + * @return ISimpleFile + */ + public function getCachedCSS(string $appName, string $fileName): ISimpleFile { + $folder = $this->appData->getFolder($appName); + $cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName); + + return $folder->getFile($cachedFileName); + } + + /** + * Check if the file is cached or not + * @param string $fileNameCSS + * @param string $app + * @return boolean + */ + private function isCached(string $fileNameCSS, string $app) { + $key = $this->config->getSystemValue('version') . '/' . $app . '/' . $fileNameCSS; + + // If the file mtime is more recent than our cached one, + // let's consider the file is properly cached + if ($cacheValue = $this->isCachedCache->get($key)) { + if ($cacheValue > $this->timeFactory->getTime()) { + return true; + } + } + $this->logger->debug("SCSSCacher::isCached $fileNameCSS isCachedCache is expired or unset", ['app' => 'scss_cacher']); + + // Creating file cache if none for further checks + try { + $folder = $this->appData->getFolder($app); + } catch (NotFoundException $e) { + $this->logger->debug("SCSSCacher::isCached app data folder for $app could not be fetched", ['app' => 'scss_cacher']); + return false; + } + + // Checking if file size is coherent + // and if one of the css dependency changed + try { + $cachedFile = $folder->getFile($fileNameCSS); + if ($cachedFile->getSize() > 0) { + $depFileName = $fileNameCSS . '.deps'; + $deps = $this->depsCache->get($folder->getName() . '-' . $depFileName); + if ($deps === null) { + $depFile = $folder->getFile($depFileName); + $deps = $depFile->getContent(); + // Set to memcache for next run + $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps); + } + $deps = json_decode($deps, true); + + foreach ((array) $deps as $file => $mtime) { + if (!file_exists($file) || filemtime($file) > $mtime) { + $this->logger->debug("SCSSCacher::isCached $fileNameCSS is not considered as cached due to deps file $file", ['app' => 'scss_cacher']); + return false; + } + } + + $this->logger->debug("SCSSCacher::isCached $fileNameCSS dependencies successfully cached for 5 minutes", ['app' => 'scss_cacher']); + // It would probably make sense to adjust this timeout to something higher and see if that has some effect then + $this->isCachedCache->set($key, $this->timeFactory->getTime() + 5 * 60); + return true; + } + $this->logger->debug("SCSSCacher::isCached $fileNameCSS is not considered as cached cacheValue: $cacheValue", ['app' => 'scss_cacher']); + return false; + } catch (NotFoundException $e) { + $this->logger->debug("SCSSCacher::isCached NotFoundException " . $e->getMessage(), ['app' => 'scss_cacher']); + return false; + } + } + + /** + * Check if the variables file has changed + * @return bool + */ + private function variablesChanged(): bool { + $injectedVariables = $this->getInjectedVariables(); + if ($this->config->getAppValue('core', 'theming.variables') !== md5($injectedVariables)) { + $this->logger->debug('SCSSCacher::variablesChanged storedVariables: ' . json_encode($this->config->getAppValue('core', 'theming.variables')) . ' currentInjectedVariables: ' . json_encode($injectedVariables), ['app' => 'scss_cacher']); + $this->config->setAppValue('core', 'theming.variables', md5($injectedVariables)); + $this->resetCache(); + return true; + } + return false; + } + + /** + * Cache the file with AppData + * + * @param string $path + * @param string $fileNameCSS + * @param string $fileNameSCSS + * @param ISimpleFolder $folder + * @param string $webDir + * @return boolean + * @throws NotPermittedException + */ + private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) { + $scss = new Compiler(); + $scss->setImportPaths([ + $path, + $this->serverRoot . '/core/css/' + ]); + + // Continue after throw + $scss->setIgnoreErrors(true); + if ($this->config->getSystemValue('debug')) { + // Debug mode + $scss->setFormatter(Expanded::class); + $scss->setLineNumberStyle(Compiler::LINE_COMMENTS); + } else { + // Compression + $scss->setFormatter(Crunched::class); + } + + try { + $cachedfile = $folder->getFile($fileNameCSS); + } catch (NotFoundException $e) { + $cachedfile = $folder->newFile($fileNameCSS); + } + + $depFileName = $fileNameCSS . '.deps'; + try { + $depFile = $folder->getFile($depFileName); + } catch (NotFoundException $e) { + $depFile = $folder->newFile($depFileName); + } + + // Compile + try { + $compiledScss = $scss->compile( + '$webroot: \'' . $this->getRoutePrefix() . '\';' . + $this->getInjectedVariables() . + '@import "variables.scss";' . + '@import "functions.scss";' . + '@import "' . $fileNameSCSS . '";'); + } catch (ParserException $e) { + $this->logger->logException($e, ['app' => 'scss_cacher']); + + return false; + } + + // Parse Icons and create related css variables + $compiledScss = $this->iconsCacher->setIconsCss($compiledScss); + + // Gzip file + try { + $gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz + } catch (NotFoundException $e) { + $gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz + } + + try { + $data = $this->rebaseUrls($compiledScss, $webDir); + $cachedfile->putContent($data); + $deps = json_encode($scss->getParsedFiles()); + $depFile->putContent($deps); + $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps); + $gzipFile->putContent(gzencode($data, 9)); + $this->logger->debug('SCSSCacher::cache ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'scss_cacher']); + + return true; + } catch (NotPermittedException $e) { + $this->logger->error('SCSSCacher::cache unable to cache: ' . $fileNameSCSS, ['app' => 'scss_cacher']); + + return false; + } + } + + /** + * Reset scss cache by deleting all generated css files + * We need to regenerate all files when variables change + */ + public function resetCache() { + $this->logger->debug('SCSSCacher::resetCache', ['app' => 'scss_cacher']); + if (!$this->lockingCache->add('resetCache', 'locked!', 120)) { + $this->logger->debug('SCSSCacher::resetCache Locked', ['app' => 'scss_cacher']); + return; + } + $this->logger->debug('SCSSCacher::resetCache Lock acquired', ['app' => 'scss_cacher']); + $this->injectedVariables = null; + + // do not clear locks + $this->cacheFactory->createDistributed('SCSS-deps-')->clear(); + $this->cacheFactory->createDistributed('SCSS-cached-')->clear(); + + $appDirectory = $this->appData->getDirectoryListing(); + foreach ($appDirectory as $folder) { + foreach ($folder->getDirectoryListing() as $file) { + try { + $file->delete(); + } catch (NotPermittedException $e) { + $this->logger->logException($e, ['message' => 'SCSSCacher::resetCache unable to delete file: ' . $file->getName(), 'app' => 'scss_cacher']); + } + } + } + $this->logger->debug('SCSSCacher::resetCache css cache cleared!', ['app' => 'scss_cacher']); + $this->lockingCache->remove('resetCache'); + $this->logger->debug('SCSSCacher::resetCache Locking removed', ['app' => 'scss_cacher']); + } + + /** + * @return string SCSS code for variables from OC_Defaults + */ + private function getInjectedVariables(): string { + if ($this->injectedVariables !== null) { + return $this->injectedVariables; + } + $variables = ''; + foreach ($this->defaults->getScssVariables() as $key => $value) { + $variables .= '$' . $key . ': ' . $value . ' !default;'; + } + + // check for valid variables / otherwise fall back to defaults + try { + $scss = new Compiler(); + $scss->compile($variables); + $this->injectedVariables = $variables; + } catch (ParserException $e) { + $this->logger->logException($e, ['app' => 'scss_cacher']); + } + + return $variables; + } + + /** + * Add the correct uri prefix to make uri valid again + * @param string $css + * @param string $webDir + * @return string + */ + private function rebaseUrls(string $css, string $webDir): string { + $re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x'; + $subst = 'url(\'' . $webDir . '/$1\')'; + + return preg_replace($re, $subst, $css); + } + + /** + * Return the cached css file uri + * @param string $appName the app name + * @param string $fileName + * @return string + */ + public function getCachedSCSS(string $appName, string $fileName): string { + $tmpfileLoc = explode('/', $fileName); + $fileName = array_pop($tmpfileLoc); + $fileName = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName); + + return substr($this->urlGenerator->linkToRoute('core.Css.getCss', [ + 'fileName' => $fileName, + 'appName' => $appName, + 'v' => $this->config->getAppValue('core', 'theming.variables', '0') + ]), \strlen(\OC::$WEBROOT) + 1); + } + + /** + * Prepend hashed base url to the css file + * @param string $cssFile + * @return string + */ + private function prependBaseurlPrefix(string $cssFile): string { + return substr(md5($this->urlGenerator->getBaseUrl() . $this->getRoutePrefix()), 0, 4) . '-' . $cssFile; + } + + private function getRoutePrefix() { + $frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); + $prefix = \OC::$WEBROOT . '/index.php'; + if ($frontControllerActive) { + $prefix = \OC::$WEBROOT; + } + return $prefix; + } + + /** + * Prepend hashed app version hash + * @param string $cssFile + * @param string $appId + * @return string + */ + private function prependVersionPrefix(string $cssFile, string $appId): string { + $appVersion = \OC_App::getAppVersion($appId); + if ($appVersion !== '0') { + return substr(md5($appVersion), 0, 4) . '-' . $cssFile; + } + $coreVersion = \OC_Util::getVersionString(); + + return substr(md5($coreVersion), 0, 4) . '-' . $cssFile; + } + + /** + * Get WebDir root + * @param string $path the css file path + * @param string $appName the app name + * @param string $serverRoot the server root path + * @param string $webRoot the nextcloud installation root path + * @return string the webDir + */ + private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string { + // Detect if path is within server root AND if path is within an app path + if (strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) { + // Get the file path within the app directory + $appDirectoryPath = explode($appName, $path)[1]; + // Remove the webroot + + return str_replace($webRoot, '', $appWebPath . $appDirectoryPath); + } + + return $webRoot . substr($path, strlen($serverRoot)); + } + + /** + * Add the icons css cache in the header if needed + * + * @return boolean true + */ + private function injectCssVariablesIfAny() { + // Inject icons vars css if any + if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) { + $this->iconsCacher->injectCss(); + } + return true; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Template/TemplateFileLocator.php b/docker/overlays/nextcloud/html/lib/private/Template/TemplateFileLocator.php new file mode 100644 index 0000000..ae73b4d --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Template/TemplateFileLocator.php @@ -0,0 +1,63 @@ + + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Template; + +class TemplateFileLocator { + protected $dirs; + private $path; + + /** + * @param string[] $dirs + */ + public function __construct($dirs) { + $this->dirs = $dirs; + } + + /** + * @param string $template + * @return string + * @throws \Exception + */ + public function find($template) { + if ($template === '') { + throw new \InvalidArgumentException('Empty template name'); + } + + foreach ($this->dirs as $dir) { + $file = $dir.$template.'.php'; + if (is_file($file)) { + $this->path = $dir; + return $file; + } + } + throw new \Exception('template file not found: template:'.$template); + } + + public function getPath() { + return $this->path; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/TemplateLayout.php b/docker/overlays/nextcloud/html/lib/private/TemplateLayout.php new file mode 100644 index 0000000..e8ebee8 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/TemplateLayout.php @@ -0,0 +1,382 @@ + + * @author Bjoern Schiessle + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Clark Tomlinson + * @author Daniel Calviño Sánchez + * @author Guillaume COMPAGNON + * @author Hendrik Leppelsack + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Julius Haertl + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Nils + * @author Remco Brenninkmeijer + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Citharel + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\Search\SearchQuery; +use OC\Template\JSCombiner; +use OC\Template\JSConfigHelper; +use OC\Template\SCSSCacher; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\Defaults; +use OCP\IConfig; +use OCP\IInitialStateService; +use OCP\INavigationManager; +use OCP\Support\Subscription\IRegistry; +use OCP\Util; + +class TemplateLayout extends \OC_Template { + private static $versionHash = ''; + + /** @var IConfig */ + private $config; + + /** @var IInitialStateService */ + private $initialState; + + /** @var INavigationManager */ + private $navigationManager; + + /** + * @param string $renderAs + * @param string $appId application id + */ + public function __construct($renderAs, $appId = '') { + + /** @var IConfig */ + $this->config = \OC::$server->get(IConfig::class); + + /** @var IInitialStateService */ + $this->initialState = \OC::$server->get(IInitialStateService::class); + + if (Util::isIE()) { + Util::addStyle('ie'); + } + + // Decide which page we show + if ($renderAs === TemplateResponse::RENDER_AS_USER) { + /** @var INavigationManager */ + $this->navigationManager = \OC::$server->get(INavigationManager::class); + + parent::__construct('core', 'layout.user'); + if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) { + $this->assign('bodyid', 'body-settings'); + } else { + $this->assign('bodyid', 'body-user'); + } + + $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry()); + $this->initialState->provideInitialState('unified-search', 'limit-default', SearchQuery::LIMIT_DEFAULT); + Util::addScript('dist/unified-search', null, true); + + // Add navigation entry + $this->assign('application', ''); + $this->assign('appid', $appId); + + $navigation = $this->navigationManager->getAll(); + $this->assign('navigation', $navigation); + $settingsNavigation = $this->navigationManager->getAll('settings'); + $this->assign('settingsnavigation', $settingsNavigation); + + foreach ($navigation as $entry) { + if ($entry['active']) { + $this->assign('application', $entry['name']); + break; + } + } + + foreach ($settingsNavigation as $entry) { + if ($entry['active']) { + $this->assign('application', $entry['name']); + break; + } + } + $userDisplayName = \OC_User::getDisplayName(); + $this->assign('user_displayname', $userDisplayName); + $this->assign('user_uid', \OC_User::getUser()); + + if (\OC_User::getUser() === false) { + $this->assign('userAvatarSet', false); + } else { + $this->assign('userAvatarSet', \OC::$server->getAvatarManager()->getAvatar(\OC_User::getUser())->exists()); + $this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0)); + } + + // check if app menu icons should be inverted + try { + /** @var \OCA\Theming\Util $util */ + $util = \OC::$server->query(\OCA\Theming\Util::class); + $this->assign('themingInvertMenu', $util->invertTextColor(\OC::$server->getThemingDefaults()->getColorPrimary())); + } catch (\OCP\AppFramework\QueryException $e) { + $this->assign('themingInvertMenu', false); + } catch (\OCP\AutoloadNotAllowedException $e) { + $this->assign('themingInvertMenu', false); + } + } elseif ($renderAs === TemplateResponse::RENDER_AS_ERROR) { + parent::__construct('core', 'layout.guest', '', false); + $this->assign('bodyid', 'body-login'); + $this->assign('user_displayname', ''); + $this->assign('user_uid', ''); + } elseif ($renderAs === TemplateResponse::RENDER_AS_GUEST) { + parent::__construct('core', 'layout.guest'); + \OC_Util::addStyle('guest'); + $this->assign('bodyid', 'body-login'); + + $userDisplayName = \OC_User::getDisplayName(); + $this->assign('user_displayname', $userDisplayName); + $this->assign('user_uid', \OC_User::getUser()); + } elseif ($renderAs === TemplateResponse::RENDER_AS_PUBLIC) { + parent::__construct('core', 'layout.public'); + $this->assign('appid', $appId); + $this->assign('bodyid', 'body-public'); + + /** @var IRegistry $subscription */ + $subscription = \OC::$server->query(IRegistry::class); + $showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true); + if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) { + $showSimpleSignup = false; + } + $this->assign('showSimpleSignUpLink', $showSimpleSignup); + } else { + parent::__construct('core', 'layout.base'); + } + // Send the language and the locale to our layouts + $lang = \OC::$server->getL10NFactory()->findLanguage(); + $locale = \OC::$server->getL10NFactory()->findLocale($lang); + + $lang = str_replace('_', '-', $lang); + $this->assign('language', $lang); + $this->assign('locale', $locale); + + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + if (empty(self::$versionHash)) { + $v = \OC_App::getAppVersions(); + $v['core'] = implode('.', \OCP\Util::getVersion()); + self::$versionHash = substr(md5(implode(',', $v)), 0, 8); + } + } else { + self::$versionHash = md5('not installed'); + } + + // Add the js files + $jsFiles = self::findJavascriptFiles(\OC_Util::$scripts); + $this->assign('jsfiles', []); + if ($this->config->getSystemValue('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) { + $jsConfigHelper = new JSConfigHelper( + \OC::$server->getL10N('lib'), + \OC::$server->query(Defaults::class), + \OC::$server->getAppManager(), + \OC::$server->getSession(), + \OC::$server->getUserSession()->getUser(), + $this->config, + \OC::$server->getGroupManager(), + \OC::$server->get(IniGetWrapper::class), + \OC::$server->getURLGenerator(), + \OC::$server->getCapabilitiesManager(), + \OC::$server->query(IInitialStateService::class) + ); + $config = $jsConfigHelper->getConfig(); + if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) { + $this->assign('inline_ocjs', $config); + } else { + $this->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash])); + } + } + foreach ($jsFiles as $info) { + $web = $info[1]; + $file = $info[2]; + $this->append('jsfiles', $web.'/'.$file . $this->getVersionHashSuffix()); + } + + try { + $pathInfo = \OC::$server->getRequest()->getPathInfo(); + } catch (\Exception $e) { + $pathInfo = ''; + } + + // Do not initialise scss appdata until we have a fully installed instance + // Do not load scss for update, errors, installation or login page + if (\OC::$server->getSystemConfig()->getValue('installed', false) + && !\OCP\Util::needUpgrade() + && $pathInfo !== '' + && !preg_match('/^\/login/', $pathInfo) + && $renderAs !== TemplateResponse::RENDER_AS_ERROR + ) { + $cssFiles = self::findStylesheetFiles(\OC_Util::$styles); + } else { + // If we ignore the scss compiler, + // we need to load the guest css fallback + \OC_Util::addStyle('guest'); + $cssFiles = self::findStylesheetFiles(\OC_Util::$styles, false); + } + + $this->assign('cssfiles', []); + $this->assign('printcssfiles', []); + $this->assign('versionHash', self::$versionHash); + foreach ($cssFiles as $info) { + $web = $info[1]; + $file = $info[2]; + + if (substr($file, -strlen('print.css')) === 'print.css') { + $this->append('printcssfiles', $web.'/'.$file . $this->getVersionHashSuffix()); + } else { + $suffix = $this->getVersionHashSuffix($web, $file); + + if (strpos($file, '?v=') == false) { + $this->append('cssfiles', $web.'/'.$file . $suffix); + } else { + $this->append('cssfiles', $web.'/'.$file . '-' . substr($suffix, 3)); + } + } + } + + $this->assign('initialStates', $this->initialState->getInitialStates()); + } + + /** + * @param string $path + * @param string $file + * @return string + */ + protected function getVersionHashSuffix($path = false, $file = false) { + if ($this->config->getSystemValue('debug', false)) { + // allows chrome workspace mapping in debug mode + return ""; + } + $themingSuffix = ''; + $v = []; + + if ($this->config->getSystemValue('installed', false)) { + if (\OC::$server->getAppManager()->isInstalled('theming')) { + $themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0'); + } + $v = \OC_App::getAppVersions(); + } + + // Try the webroot path for a match + if ($path !== false && $path !== '') { + $appName = $this->getAppNamefromPath($path); + if (array_key_exists($appName, $v)) { + $appVersion = $v[$appName]; + return '?v=' . substr(md5($appVersion), 0, 8) . $themingSuffix; + } + } + // fallback to the file path instead + if ($file !== false && $file !== '') { + $appName = $this->getAppNamefromPath($file); + if (array_key_exists($appName, $v)) { + $appVersion = $v[$appName]; + return '?v=' . substr(md5($appVersion), 0, 8) . $themingSuffix; + } + } + + return '?v=' . self::$versionHash . $themingSuffix; + } + + /** + * @param array $styles + * @return array + */ + public static function findStylesheetFiles($styles, $compileScss = true) { + // Read the selected theme from the config file + $theme = \OC_Util::getTheme(); + + if ($compileScss) { + $SCSSCacher = \OC::$server->query(SCSSCacher::class); + } else { + $SCSSCacher = null; + } + + $locator = new \OC\Template\CSSResourceLocator( + \OC::$server->getLogger(), + $theme, + [ \OC::$SERVERROOT => \OC::$WEBROOT ], + [ \OC::$SERVERROOT => \OC::$WEBROOT ], + $SCSSCacher + ); + $locator->find($styles); + return $locator->getResources(); + } + + /** + * @param string $path + * @return string|boolean + */ + public function getAppNamefromPath($path) { + if ($path !== '' && is_string($path)) { + $pathParts = explode('/', $path); + if ($pathParts[0] === 'css') { + // This is a scss request + return $pathParts[1]; + } + return end($pathParts); + } + return false; + } + + /** + * @param array $scripts + * @return array + */ + public static function findJavascriptFiles($scripts) { + // Read the selected theme from the config file + $theme = \OC_Util::getTheme(); + + $locator = new \OC\Template\JSResourceLocator( + \OC::$server->getLogger(), + $theme, + [ \OC::$SERVERROOT => \OC::$WEBROOT ], + [ \OC::$SERVERROOT => \OC::$WEBROOT ], + \OC::$server->query(JSCombiner::class) + ); + $locator->find($scripts); + return $locator->getResources(); + } + + /** + * Converts the absolute file path to a relative path from \OC::$SERVERROOT + * @param string $filePath Absolute path + * @return string Relative path + * @throws \Exception If $filePath is not under \OC::$SERVERROOT + */ + public static function convertToRelativePath($filePath) { + $relativePath = explode(\OC::$SERVERROOT, $filePath); + if (count($relativePath) !== 2) { + throw new \Exception('$filePath is not under the \OC::$SERVERROOT'); + } + + return $relativePath[1]; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/URLGenerator.php b/docker/overlays/nextcloud/html/lib/private/URLGenerator.php new file mode 100644 index 0000000..4da6a91 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/URLGenerator.php @@ -0,0 +1,274 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Felix Epp + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Haertl + * @author Julius Härtl + * @author Lukas Reschke + * @author mmccarn + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OCA\Theming\ThemingDefaults; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\Route\IRouter; +use RuntimeException; + +/** + * Class to generate URLs + */ +class URLGenerator implements IURLGenerator { + /** @var IConfig */ + private $config; + /** @var ICacheFactory */ + private $cacheFactory; + /** @var IRequest */ + private $request; + /** @var IRouter*/ + private $router; + + public function __construct(IConfig $config, + ICacheFactory $cacheFactory, + IRequest $request, + IRouter $router) { + $this->config = $config; + $this->cacheFactory = $cacheFactory; + $this->request = $request; + $this->router = $router; + } + + /** + * Creates an url using a defined route + * + * @param string $routeName + * @param array $arguments args with param=>value, will be appended to the returned url + * @return string the url + * + * Returns a url to the given route. + */ + public function linkToRoute(string $routeName, array $arguments = []): string { + return $this->router->generate($routeName, $arguments); + } + + /** + * Creates an absolute url using a defined route + * @param string $routeName + * @param array $arguments args with param=>value, will be appended to the returned url + * @return string the url + * + * Returns an absolute url to the given route. + */ + public function linkToRouteAbsolute(string $routeName, array $arguments = []): string { + return $this->getAbsoluteURL($this->linkToRoute($routeName, $arguments)); + } + + public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string { + $route = $this->router->generate('ocs.'.$routeName, $arguments, false); + + $indexPhpPos = strpos($route, '/index.php/'); + if ($indexPhpPos !== false) { + $route = substr($route, $indexPhpPos + 10); + } + + $route = substr($route, 7); + $route = '/ocs/v2.php' . $route; + + return $this->getAbsoluteURL($route); + } + + /** + * Creates an url + * + * @param string $appName app + * @param string $file file + * @param array $args array with param=>value, will be appended to the returned url + * The value of $args will be urlencoded + * @return string the url + * + * Returns a url to the given app and file. + */ + public function linkTo(string $appName, string $file, array $args = []): string { + $frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); + + if ($appName !== '') { + $app_path = \OC_App::getAppPath($appName); + // Check if the app is in the app folder + if ($app_path && file_exists($app_path . '/' . $file)) { + if (substr($file, -3) === 'php') { + $urlLinkTo = \OC::$WEBROOT . '/index.php/apps/' . $appName; + if ($frontControllerActive) { + $urlLinkTo = \OC::$WEBROOT . '/apps/' . $appName; + } + $urlLinkTo .= ($file !== 'index.php') ? '/' . $file : ''; + } else { + $urlLinkTo = \OC_App::getAppWebPath($appName) . '/' . $file; + } + } else { + $urlLinkTo = \OC::$WEBROOT . '/' . $appName . '/' . $file; + } + } else { + if (file_exists(\OC::$SERVERROOT . '/core/' . $file)) { + $urlLinkTo = \OC::$WEBROOT . '/core/' . $file; + } else { + if ($frontControllerActive && $file === 'index.php') { + $urlLinkTo = \OC::$WEBROOT . '/'; + } else { + $urlLinkTo = \OC::$WEBROOT . '/' . $file; + } + } + } + + if ($args && $query = http_build_query($args, '', '&')) { + $urlLinkTo .= '?' . $query; + } + + return $urlLinkTo; + } + + /** + * Creates path to an image + * + * @param string $appName app + * @param string $file image name + * @throws \RuntimeException If the image does not exist + * @return string the url + * + * Returns the path to the image. + */ + public function imagePath(string $appName, string $file): string { + $cache = $this->cacheFactory->createDistributed('imagePath-'.md5($this->getBaseUrl()).'-'); + $cacheKey = $appName.'-'.$file; + if ($key = $cache->get($cacheKey)) { + return $key; + } + + // Read the selected theme from the config file + $theme = \OC_Util::getTheme(); + + //if a theme has a png but not an svg always use the png + $basename = substr(basename($file),0,-4); + + $appPath = \OC_App::getAppPath($appName); + + // Check if the app is in the app folder + $path = ''; + $themingEnabled = $this->config->getSystemValue('installed', false) && \OCP\App::isEnabled('theming') && \OC_App::isAppLoaded('theming'); + $themingImagePath = false; + if ($themingEnabled) { + $themingDefaults = \OC::$server->getThemingDefaults(); + if ($themingDefaults instanceof ThemingDefaults) { + $themingImagePath = $themingDefaults->replaceImagePath($appName, $file); + } + } + + if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$file"; + } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.png")) { + $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$basename.png"; + } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$file"; + } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.png"))) { + $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$basename.png"; + } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$file"; + } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) { + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; + } elseif ($themingEnabled && $themingImagePath) { + $path = $themingImagePath; + } elseif ($appPath && file_exists($appPath . "/img/$file")) { + $path = \OC_App::getAppWebPath($appName) . "/img/$file"; + } elseif ($appPath && !file_exists($appPath . "/img/$basename.svg") + && file_exists($appPath . "/img/$basename.png")) { + $path = \OC_App::getAppWebPath($appName) . "/img/$basename.png"; + } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/$appName/img/$file"; + } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.png"))) { + $path = \OC::$WEBROOT . "/$appName/img/$basename.png"; + } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$file")) { + $path = \OC::$WEBROOT . "/core/img/$file"; + } elseif (!file_exists(\OC::$SERVERROOT . "/core/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/core/img/$basename.png")) { + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; + } + + if ($path !== '') { + $cache->set($cacheKey, $path); + return $path; + } + + throw new RuntimeException('image not found: image:' . $file . ' webroot:' . \OC::$WEBROOT . ' serverroot:' . \OC::$SERVERROOT); + } + + + /** + * Makes an URL absolute + * @param string $url the url in the ownCloud host + * @return string the absolute version of the url + */ + public function getAbsoluteURL(string $url): string { + $separator = strpos($url, '/') === 0 ? '' : '/'; + + if (\OC::$CLI && !\defined('PHPUNIT_RUN')) { + return rtrim($this->config->getSystemValue('overwrite.cli.url'), '/') . '/' . ltrim($url, '/'); + } + // The ownCloud web root can already be prepended. + if (\OC::$WEBROOT !== '' && strpos($url, \OC::$WEBROOT) === 0) { + $url = substr($url, \strlen(\OC::$WEBROOT)); + } + + return $this->getBaseUrl() . $separator . $url; + } + + /** + * @param string $key + * @return string url to the online documentation + */ + public function linkToDocs(string $key): string { + $theme = \OC::$server->getThemingDefaults(); + return $theme->buildDocLinkToKey($key); + } + + /** + * @return string base url of the current request + */ + public function getBaseUrl(): string { + return $this->request->getServerProtocol() . '://' . $this->request->getServerHost() . \OC::$WEBROOT; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Updater.php b/docker/overlays/nextcloud/html/lib/private/Updater.php new file mode 100644 index 0000000..4b5d02a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Updater.php @@ -0,0 +1,624 @@ + + * + * @author Arthur Schiwon + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Frank Karlitschek + * @author Georg Ehrke + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Steffen Lindner + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +use OC\DB\MigrationService; +use OC\Hooks\BasicEmitter; +use OC\IntegrityCheck\Checker; +use OC_App; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Util; +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Class that handles autoupdating of ownCloud + * + * Hooks provided in scope \OC\Updater + * - maintenanceStart() + * - maintenanceEnd() + * - dbUpgrade() + * - failure(string $message) + */ +class Updater extends BasicEmitter { + + /** @var ILogger $log */ + private $log; + + /** @var IConfig */ + private $config; + + /** @var Checker */ + private $checker; + + /** @var Installer */ + private $installer; + + private $logLevelNames = [ + 0 => 'Debug', + 1 => 'Info', + 2 => 'Warning', + 3 => 'Error', + 4 => 'Fatal', + ]; + + /** + * @param IConfig $config + * @param Checker $checker + * @param ILogger $log + * @param Installer $installer + */ + public function __construct(IConfig $config, + Checker $checker, + ILogger $log = null, + Installer $installer) { + $this->log = $log; + $this->config = $config; + $this->checker = $checker; + $this->installer = $installer; + } + + /** + * runs the update actions in maintenance mode, does not upgrade the source files + * except the main .htaccess file + * + * @return bool true if the operation succeeded, false otherwise + */ + public function upgrade() { + $this->emitRepairEvents(); + $this->logAllEvents(); + + $logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN); + $this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]); + $this->config->setSystemValue('loglevel', ILogger::DEBUG); + + $wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance'); + + if (!$wasMaintenanceModeEnabled) { + $this->config->setSystemValue('maintenance', true); + $this->emit('\OC\Updater', 'maintenanceEnabled'); + } + + // Clear CAN_INSTALL file if not on git + if (\OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL')) { + if (!unlink(\OC::$configDir . '/CAN_INSTALL')) { + $this->log->error('Could not cleanup CAN_INSTALL from your config folder. Please remove this file manually.'); + } + } + + $installedVersion = $this->config->getSystemValue('version', '0.0.0'); + $currentVersion = implode('.', \OCP\Util::getVersion()); + + $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, ['app' => 'core']); + + $success = true; + try { + $this->doUpgrade($currentVersion, $installedVersion); + } catch (HintException $exception) { + $this->log->logException($exception, ['app' => 'core']); + $this->emit('\OC\Updater', 'failure', [$exception->getMessage() . ': ' .$exception->getHint()]); + $success = false; + } catch (\Exception $exception) { + $this->log->logException($exception, ['app' => 'core']); + $this->emit('\OC\Updater', 'failure', [get_class($exception) . ': ' .$exception->getMessage()]); + $success = false; + } + + $this->emit('\OC\Updater', 'updateEnd', [$success]); + + if (!$wasMaintenanceModeEnabled && $success) { + $this->config->setSystemValue('maintenance', false); + $this->emit('\OC\Updater', 'maintenanceDisabled'); + } else { + $this->emit('\OC\Updater', 'maintenanceActive'); + } + + $this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]); + $this->config->setSystemValue('loglevel', $logLevel); + $this->config->setSystemValue('installed', true); + + return $success; + } + + /** + * Return version from which this version is allowed to upgrade from + * + * @return array allowed previous versions per vendor + */ + private function getAllowedPreviousVersions() { + // this should really be a JSON file + require \OC::$SERVERROOT . '/version.php'; + /** @var array $OC_VersionCanBeUpgradedFrom */ + return $OC_VersionCanBeUpgradedFrom; + } + + /** + * Return vendor from which this version was published + * + * @return string Get the vendor + */ + private function getVendor() { + // this should really be a JSON file + require \OC::$SERVERROOT . '/version.php'; + /** @var string $vendor */ + return (string) $vendor; + } + + /** + * Whether an upgrade to a specified version is possible + * @param string $oldVersion + * @param string $newVersion + * @param array $allowedPreviousVersions + * @return bool + */ + public function isUpgradePossible($oldVersion, $newVersion, array $allowedPreviousVersions) { + $version = explode('.', $oldVersion); + $majorMinor = $version[0] . '.' . $version[1]; + + $currentVendor = $this->config->getAppValue('core', 'vendor', ''); + + // Vendor was not set correctly on install, so we have to white-list known versions + if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) { + $currentVendor = 'owncloud'; + } + + if ($currentVendor === 'nextcloud') { + return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) + && (version_compare($oldVersion, $newVersion, '<=') || + $this->config->getSystemValue('debug', false)); + } + + // Check if the instance can be migrated + return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) || + isset($allowedPreviousVersions[$currentVendor][$oldVersion]); + } + + /** + * runs the update actions in maintenance mode, does not upgrade the source files + * except the main .htaccess file + * + * @param string $currentVersion current version to upgrade to + * @param string $installedVersion previous version from which to upgrade from + * + * @throws \Exception + */ + private function doUpgrade($currentVersion, $installedVersion) { + // Stop update if the update is over several major versions + $allowedPreviousVersions = $this->getAllowedPreviousVersions(); + if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) { + throw new \Exception('Updates between multiple major versions and downgrades are unsupported.'); + } + + // Update .htaccess files + try { + Setup::updateHtaccess(); + Setup::protectDataDirectory(); + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + + // create empty file in data dir, so we can later find + // out that this is indeed an ownCloud data directory + // (in case it didn't exist before) + file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', ''); + + // pre-upgrade repairs + $repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher()); + $repair->run(); + + $this->doCoreUpgrade(); + + try { + // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378 + Setup::installBackgroundJobs(); + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + + // update all shipped apps + $this->checkAppsRequirements(); + $this->doAppUpgrade(); + + // Update the appfetchers version so it downloads the correct list from the appstore + \OC::$server->getAppFetcher()->setVersion($currentVersion); + + // upgrade appstore apps + $this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps()); + $autoDisabledApps = \OC::$server->getAppManager()->getAutoDisabledApps(); + $this->upgradeAppStoreApps($autoDisabledApps, true); + + // install new shipped apps on upgrade + OC_App::loadApps(['authentication']); + $errors = Installer::installShippedApps(true); + foreach ($errors as $appId => $exception) { + /** @var \Exception $exception */ + $this->log->logException($exception, ['app' => $appId]); + $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]); + } + + // post-upgrade repairs + $repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher()); + $repair->run(); + + //Invalidate update feed + $this->config->setAppValue('core', 'lastupdatedat', 0); + + // Check for code integrity if not disabled + if (\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) { + $this->emit('\OC\Updater', 'startCheckCodeIntegrity'); + $this->checker->runInstanceVerification(); + $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity'); + } + + // only set the final version if everything went well + $this->config->setSystemValue('version', implode('.', Util::getVersion())); + $this->config->setAppValue('core', 'vendor', $this->getVendor()); + } + + protected function doCoreUpgrade() { + $this->emit('\OC\Updater', 'dbUpgradeBefore'); + + // execute core migrations + $ms = new MigrationService('core', \OC::$server->getDatabaseConnection()); + $ms->migrate(); + + $this->emit('\OC\Updater', 'dbUpgrade'); + } + + /** + * @param string $version the oc version to check app compatibility with + */ + protected function checkAppUpgrade($version) { + $apps = \OC_App::getEnabledApps(); + $this->emit('\OC\Updater', 'appUpgradeCheckBefore'); + + $appManager = \OC::$server->getAppManager(); + foreach ($apps as $appId) { + $info = \OC_App::getAppInfo($appId); + $compatible = \OC_App::isAppCompatible($version, $info); + $isShipped = $appManager->isShipped($appId); + + if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) { + /** + * FIXME: The preupdate check is performed before the database migration, otherwise database changes + * are not possible anymore within it. - Consider this when touching the code. + * @link https://github.com/owncloud/core/issues/10980 + * @see \OC_App::updateApp + */ + if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) { + $this->includePreUpdate($appId); + } + if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) { + $this->emit('\OC\Updater', 'appSimulateUpdate', [$appId]); + \OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml'); + } + } + } + + $this->emit('\OC\Updater', 'appUpgradeCheck'); + } + + /** + * Includes the pre-update file. Done here to prevent namespace mixups. + * @param string $appId + */ + private function includePreUpdate($appId) { + include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php'; + } + + /** + * upgrades all apps within a major ownCloud upgrade. Also loads "priority" + * (types authentication, filesystem, logging, in that order) afterwards. + * + * @throws NeedsUpdateException + */ + protected function doAppUpgrade() { + $apps = \OC_App::getEnabledApps(); + $priorityTypes = ['authentication', 'filesystem', 'logging']; + $pseudoOtherType = 'other'; + $stacks = [$pseudoOtherType => []]; + + foreach ($apps as $appId) { + $priorityType = false; + foreach ($priorityTypes as $type) { + if (!isset($stacks[$type])) { + $stacks[$type] = []; + } + if (\OC_App::isType($appId, [$type])) { + $stacks[$type][] = $appId; + $priorityType = true; + break; + } + } + if (!$priorityType) { + $stacks[$pseudoOtherType][] = $appId; + } + } + foreach ($stacks as $type => $stack) { + foreach ($stack as $appId) { + if (\OC_App::shouldUpgrade($appId)) { + $this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]); + \OC_App::updateApp($appId); + $this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]); + } + if ($type !== $pseudoOtherType) { + // load authentication, filesystem and logging apps after + // upgrading them. Other apps my need to rely on modifying + // user and/or filesystem aspects. + \OC_App::loadApp($appId); + } + } + } + } + + /** + * check if the current enabled apps are compatible with the current + * ownCloud version. disable them if not. + * This is important if you upgrade ownCloud and have non ported 3rd + * party apps installed. + * + * @return array + * @throws \Exception + */ + private function checkAppsRequirements() { + $isCoreUpgrade = $this->isCodeUpgrade(); + $apps = OC_App::getEnabledApps(); + $version = implode('.', Util::getVersion()); + $disabledApps = []; + $appManager = \OC::$server->getAppManager(); + foreach ($apps as $app) { + // check if the app is compatible with this version of ownCloud + $info = OC_App::getAppInfo($app); + if ($info === null || !OC_App::isAppCompatible($version, $info)) { + if ($appManager->isShipped($app)) { + throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update'); + } + \OC::$server->getAppManager()->disableApp($app, true); + $this->emit('\OC\Updater', 'incompatibleAppDisabled', [$app]); + } + // no need to disable any app in case this is a non-core upgrade + if (!$isCoreUpgrade) { + continue; + } + // shipped apps will remain enabled + if ($appManager->isShipped($app)) { + continue; + } + // authentication and session apps will remain enabled as well + if (OC_App::isType($app, ['session', 'authentication'])) { + continue; + } + } + return $disabledApps; + } + + /** + * @return bool + */ + private function isCodeUpgrade() { + $installedVersion = $this->config->getSystemValue('version', '0.0.0'); + $currentVersion = implode('.', Util::getVersion()); + if (version_compare($currentVersion, $installedVersion, '>')) { + return true; + } + return false; + } + + /** + * @param array $disabledApps + * @param bool $reenable + * @throws \Exception + */ + private function upgradeAppStoreApps(array $disabledApps, $reenable = false) { + foreach ($disabledApps as $app) { + try { + $this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]); + if ($this->installer->isUpdateAvailable($app)) { + $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]); + $this->installer->updateAppstoreApp($app); + } + $this->emit('\OC\Updater', 'checkAppStoreApp', [$app]); + + if ($reenable) { + $ocApp = new \OC_App(); + $ocApp->enable($app); + } + } catch (\Exception $ex) { + $this->log->logException($ex, ['app' => 'core']); + } + } + } + + /** + * Forward messages emitted by the repair routine + */ + private function emitRepairEvents() { + $dispatcher = \OC::$server->getEventDispatcher(); + $dispatcher->addListener('\OC\Repair::warning', function ($event) { + if ($event instanceof GenericEvent) { + $this->emit('\OC\Updater', 'repairWarning', $event->getArguments()); + } + }); + $dispatcher->addListener('\OC\Repair::error', function ($event) { + if ($event instanceof GenericEvent) { + $this->emit('\OC\Updater', 'repairError', $event->getArguments()); + } + }); + $dispatcher->addListener('\OC\Repair::info', function ($event) { + if ($event instanceof GenericEvent) { + $this->emit('\OC\Updater', 'repairInfo', $event->getArguments()); + } + }); + $dispatcher->addListener('\OC\Repair::step', function ($event) { + if ($event instanceof GenericEvent) { + $this->emit('\OC\Updater', 'repairStep', $event->getArguments()); + } + }); + } + + private function logAllEvents() { + $log = $this->log; + + $dispatcher = \OC::$server->getEventDispatcher(); + $dispatcher->addListener('\OC\DB\Migrator::executeSql', function ($event) use ($log) { + if (!$event instanceof GenericEvent) { + return; + } + $log->info('\OC\DB\Migrator::executeSql: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']); + }); + $dispatcher->addListener('\OC\DB\Migrator::checkTable', function ($event) use ($log) { + if (!$event instanceof GenericEvent) { + return; + } + $log->info('\OC\DB\Migrator::checkTable: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']); + }); + + $repairListener = function ($event) use ($log) { + if (!$event instanceof GenericEvent) { + return; + } + switch ($event->getSubject()) { + case '\OC\Repair::startProgress': + $log->info('\OC\Repair::startProgress: Starting ... ' . $event->getArgument(1) . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']); + break; + case '\OC\Repair::advance': + $desc = $event->getArgument(1); + if (empty($desc)) { + $desc = ''; + } + $log->info('\OC\Repair::advance: ' . $desc . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']); + + break; + case '\OC\Repair::finishProgress': + $log->info('\OC\Repair::finishProgress', ['app' => 'updater']); + break; + case '\OC\Repair::step': + $log->info('\OC\Repair::step: Repair step: ' . $event->getArgument(0), ['app' => 'updater']); + break; + case '\OC\Repair::info': + $log->info('\OC\Repair::info: Repair info: ' . $event->getArgument(0), ['app' => 'updater']); + break; + case '\OC\Repair::warning': + $log->warning('\OC\Repair::warning: Repair warning: ' . $event->getArgument(0), ['app' => 'updater']); + break; + case '\OC\Repair::error': + $log->error('\OC\Repair::error: Repair error: ' . $event->getArgument(0), ['app' => 'updater']); + break; + } + }; + + $dispatcher->addListener('\OC\Repair::startProgress', $repairListener); + $dispatcher->addListener('\OC\Repair::advance', $repairListener); + $dispatcher->addListener('\OC\Repair::finishProgress', $repairListener); + $dispatcher->addListener('\OC\Repair::step', $repairListener); + $dispatcher->addListener('\OC\Repair::info', $repairListener); + $dispatcher->addListener('\OC\Repair::warning', $repairListener); + $dispatcher->addListener('\OC\Repair::error', $repairListener); + + + $this->listen('\OC\Updater', 'maintenanceEnabled', function () use ($log) { + $log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'maintenanceDisabled', function () use ($log) { + $log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'maintenanceActive', function () use ($log) { + $log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'updateEnd', function ($success) use ($log) { + if ($success) { + $log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']); + } else { + $log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']); + } + }); + $this->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($log) { + $log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'dbUpgrade', function () use ($log) { + $log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use ($log) { + $log->info('\OC\Updater::dbSimulateUpgradeBefore: Checking whether the database schema can be updated (this can take a long time depending on the database size)', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'dbSimulateUpgrade', function () use ($log) { + $log->info('\OC\Updater::dbSimulateUpgrade: Checked database schema update', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use ($log) { + $log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use ($log) { + $log->info('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($log) { + $log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use ($log) { + $log->info('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($log) { + $log->info('\OC\Updater::appUpgradeCheckBefore: Checking updates of apps', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) { + $log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'appUpgradeCheck', function () use ($log) { + $log->info('\OC\Updater::appUpgradeCheck: Checked database schema update for apps', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) { + $log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) { + $log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'failure', function ($message) use ($log) { + $log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'setDebugLogLevel', function () use ($log) { + $log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($log) { + $log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($log) { + $log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']); + }); + $this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($log) { + $log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']); + }); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Updater/ChangesCheck.php b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesCheck.php new file mode 100644 index 0000000..62b1e04 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesCheck.php @@ -0,0 +1,174 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Daniel Kesselberg + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Updater; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\Http\Client\IClientService; +use OCP\Http\Client\IResponse; +use OCP\ILogger; + +class ChangesCheck { + /** @var IClientService */ + protected $clientService; + /** @var ChangesMapper */ + private $mapper; + /** @var ILogger */ + private $logger; + + public const RESPONSE_NO_CONTENT = 0; + public const RESPONSE_USE_CACHE = 1; + public const RESPONSE_HAS_CONTENT = 2; + + public function __construct(IClientService $clientService, ChangesMapper $mapper, ILogger $logger) { + $this->clientService = $clientService; + $this->mapper = $mapper; + $this->logger = $logger; + } + + /** + * @throws DoesNotExistException + */ + public function getChangesForVersion(string $version): array { + $version = $this->normalizeVersion($version); + $changesInfo = $this->mapper->getChanges($version); + $changesData = json_decode($changesInfo->getData(), true); + if (empty($changesData)) { + throw new DoesNotExistException('Unable to decode changes info'); + } + return $changesData; + } + + /** + * @throws \Exception + */ + public function check(string $uri, string $version): array { + try { + $version = $this->normalizeVersion($version); + $changesInfo = $this->mapper->getChanges($version); + if ($changesInfo->getLastCheck() + 1800 > time()) { + return json_decode($changesInfo->getData(), true); + } + } catch (DoesNotExistException $e) { + $changesInfo = new ChangesResult(); + } + + $response = $this->queryChangesServer($uri, $changesInfo); + + switch ($this->evaluateResponse($response)) { + case self::RESPONSE_NO_CONTENT: + return []; + case self::RESPONSE_USE_CACHE: + return json_decode($changesInfo->getData(), true); + case self::RESPONSE_HAS_CONTENT: + default: + $data = $this->extractData($response->getBody()); + $changesInfo->setData(json_encode($data)); + $changesInfo->setEtag($response->getHeader('Etag')); + $this->cacheResult($changesInfo, $version); + + return $data; + } + } + + protected function evaluateResponse(IResponse $response): int { + if ($response->getStatusCode() === 304) { + return self::RESPONSE_USE_CACHE; + } elseif ($response->getStatusCode() === 404) { + return self::RESPONSE_NO_CONTENT; + } elseif ($response->getStatusCode() === 200) { + return self::RESPONSE_HAS_CONTENT; + } + $this->logger->debug('Unexpected return code {code} from changelog server', [ + 'app' => 'core', + 'code' => $response->getStatusCode(), + ]); + return self::RESPONSE_NO_CONTENT; + } + + protected function cacheResult(ChangesResult $entry, string $version) { + if ($entry->getVersion() === $version) { + $this->mapper->update($entry); + } else { + $entry->setVersion($version); + $this->mapper->insert($entry); + } + } + + /** + * @throws \Exception + */ + protected function queryChangesServer(string $uri, ChangesResult $entry): IResponse { + $headers = []; + if ($entry->getEtag() !== '') { + $headers['If-None-Match'] = [$entry->getEtag()]; + } + + $entry->setLastCheck(time()); + $client = $this->clientService->newClient(); + return $client->get($uri, [ + 'headers' => $headers, + ]); + } + + protected function extractData($body):array { + $data = []; + if ($body) { + $loadEntities = libxml_disable_entity_loader(true); + $xml = @simplexml_load_string($body); + libxml_disable_entity_loader($loadEntities); + if ($xml !== false) { + $data['changelogURL'] = (string)$xml->changelog['href']; + $data['whatsNew'] = []; + foreach ($xml->whatsNew as $infoSet) { + $data['whatsNew'][(string)$infoSet['lang']] = [ + 'regular' => (array)$infoSet->regular->item, + 'admin' => (array)$infoSet->admin->item, + ]; + } + } else { + libxml_clear_errors(); + } + } + return $data; + } + + /** + * returns a x.y.z form of the provided version. Extra numbers will be + * omitted, missing ones added as zeros. + */ + public function normalizeVersion(string $version): string { + $versionNumbers = array_slice(explode('.', $version), 0, 3); + $versionNumbers[0] = $versionNumbers[0] ?: '0'; // deal with empty input + while (count($versionNumbers) < 3) { + // changelog server expects x.y.z, pad 0 if it is too short + $versionNumbers[] = 0; + } + return implode('.', $versionNumbers); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Updater/ChangesMapper.php b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesMapper.php new file mode 100644 index 0000000..83a695b --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesMapper.php @@ -0,0 +1,60 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Updater; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class ChangesMapper extends QBMapper { + public const TABLE_NAME = 'whats_new'; + + public function __construct(IDBConnection $db) { + parent::__construct($db, self::TABLE_NAME); + } + + /** + * @throws DoesNotExistException + */ + public function getChanges(string $version): ChangesResult { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('*') + ->from(self::TABLE_NAME) + ->where($qb->expr()->eq('version', $qb->createNamedParameter($version))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('Changes info is not present'); + } + return ChangesResult::fromRow($data); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Updater/ChangesResult.php b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesResult.php new file mode 100644 index 0000000..dfd3aaa --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Updater/ChangesResult.php @@ -0,0 +1,63 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Updater; + +use OCP\AppFramework\Db\Entity; + +/** + * Class ChangesResult + * + * @package OC\Updater + * @method string getVersion()=1 + * @method void setVersion(string $version) + * @method string getEtag() + * @method void setEtag(string $etag) + * @method int getLastCheck() + * @method void setLastCheck(int $lastCheck) + * @method string getData() + * @method void setData(string $data) + */ +class ChangesResult extends Entity { + /** @var string */ + protected $version = ''; + + /** @var string */ + protected $etag = ''; + + /** @var int */ + protected $lastCheck = 0; + + /** @var string */ + protected $data = ''; + + public function __construct() { + $this->addType('version', 'string'); + $this->addType('etag', 'string'); + $this->addType('lastCheck', 'int'); + $this->addType('data', 'string'); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/Updater/VersionCheck.php b/docker/overlays/nextcloud/html/lib/private/Updater/VersionCheck.php new file mode 100644 index 0000000..7b1f134 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/Updater/VersionCheck.php @@ -0,0 +1,131 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Updater; + +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\Util; + +class VersionCheck { + + /** @var IClientService */ + private $clientService; + + /** @var IConfig */ + private $config; + + /** + * @param IClientService $clientService + * @param IConfig $config + */ + public function __construct(IClientService $clientService, + IConfig $config) { + $this->clientService = $clientService; + $this->config = $config; + } + + + /** + * Check if a new version is available + * + * @return array|bool + */ + public function check() { + // If this server is set to have no internet connection this is all not needed + if (!$this->config->getSystemValueBool('has_internet_connection', true)) { + return false; + } + + // Look up the cache - it is invalidated all 30 minutes + if (((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) { + return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true); + } + + $updaterUrl = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/updater_server/'); + + $this->config->setAppValue('core', 'lastupdatedat', time()); + + if ($this->config->getAppValue('core', 'installedat', '') === '') { + $this->config->setAppValue('core', 'installedat', microtime(true)); + } + + $version = Util::getVersion(); + $version['installed'] = $this->config->getAppValue('core', 'installedat'); + $version['updated'] = $this->config->getAppValue('core', 'lastupdatedat'); + $version['updatechannel'] = \OC_Util::getChannel(); + $version['edition'] = ''; + $version['build'] = \OC_Util::getBuild(); + $version['php_major'] = PHP_MAJOR_VERSION; + $version['php_minor'] = PHP_MINOR_VERSION; + $version['php_release'] = PHP_RELEASE_VERSION; + $versionString = implode('x', $version); + + //fetch xml data from updater + $url = $updaterUrl . '?version=' . $versionString; + + $tmp = []; + try { + $xml = $this->getUrlContent($url); + } catch (\Exception $e) { + return false; + } + + if ($xml) { + $loadEntities = libxml_disable_entity_loader(true); + $data = @simplexml_load_string($xml); + libxml_disable_entity_loader($loadEntities); + if ($data !== false) { + $tmp['version'] = (string)$data->version; + $tmp['versionstring'] = (string)$data->versionstring; + $tmp['url'] = (string)$data->url; + $tmp['web'] = (string)$data->web; + $tmp['changes'] = isset($data->changes) ? (string)$data->changes : ''; + $tmp['autoupdater'] = (string)$data->autoupdater; + $tmp['eol'] = isset($data->eol) ? (string)$data->eol : '0'; + } else { + libxml_clear_errors(); + } + } + + // Cache the result + $this->config->setAppValue('core', 'lastupdateResult', json_encode($tmp)); + return $tmp; + } + + /** + * @codeCoverageIgnore + * @param string $url + * @return resource|string + * @throws \Exception + */ + protected function getUrlContent($url) { + $client = $this->clientService->newClient(); + $response = $client->get($url); + return $response->getBody(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/Backend.php b/docker/overlays/nextcloud/html/lib/private/User/Backend.php new file mode 100644 index 0000000..4774482 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/Backend.php @@ -0,0 +1,166 @@ + + * @author Jörn Friedrich Dreyer + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +use OCP\UserInterface; + +/** + * Abstract base class for user management. Provides methods for querying backend + * capabilities. + */ +abstract class Backend implements UserInterface { + /** + * error code for functions not provided by the user backend + */ + public const NOT_IMPLEMENTED = -501; + + /** + * actions that user backends can define + */ + public const CREATE_USER = 1; // 1 << 0 + public const SET_PASSWORD = 16; // 1 << 4 + public const CHECK_PASSWORD = 256; // 1 << 8 + public const GET_HOME = 4096; // 1 << 12 + public const GET_DISPLAYNAME = 65536; // 1 << 16 + public const SET_DISPLAYNAME = 1048576; // 1 << 20 + public const PROVIDE_AVATAR = 16777216; // 1 << 24 + public const COUNT_USERS = 268435456; // 1 << 28 + + protected $possibleActions = [ + self::CREATE_USER => 'createUser', + self::SET_PASSWORD => 'setPassword', + self::CHECK_PASSWORD => 'checkPassword', + self::GET_HOME => 'getHome', + self::GET_DISPLAYNAME => 'getDisplayName', + self::SET_DISPLAYNAME => 'setDisplayName', + self::PROVIDE_AVATAR => 'canChangeAvatar', + self::COUNT_USERS => 'countUsers', + ]; + + /** + * Get all supported actions + * @return int bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with self::CREATE_USER etc. + */ + public function getSupportedActions() { + $actions = 0; + foreach ($this->possibleActions as $action => $methodName) { + if (method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with self::CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)($this->getSupportedActions() & $actions); + } + + /** + * delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = null, $offset = null) { + return []; + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return false; + } + + /** + * get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + return false; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid) { + return $uid; + } + + /** + * Get a list of all display names and user ids. + * + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = []; + $users = $this->getUsers($search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user] = $user; + } + return $displayNames; + } + + /** + * Check if a user list is available or not + * @return boolean if users can be listed or not + */ + public function hasUserListings() { + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/Database.php b/docker/overlays/nextcloud/html/lib/private/User/Database.php new file mode 100644 index 0000000..2353aee --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/Database.php @@ -0,0 +1,498 @@ + + * @author Aldo "xoen" Giambelluca + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author fabian + * @author Georg Ehrke + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Loki3000 + * @author Lukas Reschke + * @author Morris Jobke + * @author nishiki + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `users` ( + * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + * PRIMARY KEY (`uid`) + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + */ + +namespace OC\User; + +use OC\Cache\CappedMemoryCache; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IDBConnection; +use OCP\Security\Events\ValidatePasswordPolicyEvent; +use OCP\User\Backend\ABackend; +use OCP\User\Backend\ICheckPasswordBackend; +use OCP\User\Backend\ICountUsersBackend; +use OCP\User\Backend\ICreateUserBackend; +use OCP\User\Backend\IGetDisplayNameBackend; +use OCP\User\Backend\IGetHomeBackend; +use OCP\User\Backend\IGetRealUIDBackend; +use OCP\User\Backend\ISetDisplayNameBackend; +use OCP\User\Backend\ISetPasswordBackend; + +/** + * Class for user management in a SQL Database (e.g. MySQL, SQLite) + */ +class Database extends ABackend implements + ICreateUserBackend, + ISetPasswordBackend, + ISetDisplayNameBackend, + IGetDisplayNameBackend, + ICheckPasswordBackend, + IGetHomeBackend, + ICountUsersBackend, + IGetRealUIDBackend { + /** @var CappedMemoryCache */ + private $cache; + + /** @var IEventDispatcher */ + private $eventDispatcher; + + /** @var IDBConnection */ + private $dbConn; + + /** @var string */ + private $table; + + /** + * \OC\User\Database constructor. + * + * @param IEventDispatcher $eventDispatcher + * @param string $table + */ + public function __construct($eventDispatcher = null, $table = 'users') { + $this->cache = new CappedMemoryCache(); + $this->table = $table; + $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class); + } + + /** + * FIXME: This function should not be required! + */ + private function fixDI() { + if ($this->dbConn === null) { + $this->dbConn = \OC::$server->getDatabaseConnection(); + } + } + + /** + * Create a new user + * + * @param string $uid The username of the user to create + * @param string $password The password of the new user + * @return bool + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser(string $uid, string $password): bool { + $this->fixDI(); + + if (!$this->userExists($uid)) { + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); + + $qb = $this->dbConn->getQueryBuilder(); + $qb->insert($this->table) + ->values([ + 'uid' => $qb->createNamedParameter($uid), + 'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)), + 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)), + ]); + + $result = $qb->execute(); + + // Clear cache + unset($this->cache[$uid]); + + return $result ? true : false; + } + + return false; + } + + /** + * delete a user + * + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + $this->fixDI(); + + // Delete user-group-relation + $query = $this->dbConn->getQueryBuilder(); + $query->delete($this->table) + ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); + $result = $query->execute(); + + if (isset($this->cache[$uid])) { + unset($this->cache[$uid]); + } + + return $result ? true : false; + } + + private function updatePassword(string $uid, string $passwordHash): bool { + $query = $this->dbConn->getQueryBuilder(); + $query->update($this->table) + ->set('password', $query->createNamedParameter($passwordHash)) + ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); + $result = $query->execute(); + + return $result ? true : false; + } + + /** + * Set password + * + * @param string $uid The username + * @param string $password The new password + * @return bool + * + * Change the password of a user + */ + public function setPassword(string $uid, string $password): bool { + $this->fixDI(); + + if ($this->userExists($uid)) { + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); + + $hasher = \OC::$server->getHasher(); + $hashedPassword = $hasher->hash($password); + + return $this->updatePassword($uid, $hashedPassword); + } + + return false; + } + + /** + * Set display name + * + * @param string $uid The username + * @param string $displayName The new display name + * @return bool + * + * Change the display name of a user + */ + public function setDisplayName(string $uid, string $displayName): bool { + $this->fixDI(); + + if ($this->userExists($uid)) { + $query = $this->dbConn->getQueryBuilder(); + $query->update($this->table) + ->set('displayname', $query->createNamedParameter($displayName)) + ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid)))); + $query->execute(); + + $this->cache[$uid]['displayname'] = $displayName; + + return true; + } + + return false; + } + + /** + * get display name of the user + * + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid): string { + $uid = (string)$uid; + $this->loadUser($uid); + return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname']; + } + + /** + * Get a list of all display names and user ids. + * + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $limit = $this->fixLimit($limit); + + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + + $query->select('uid', 'displayname') + ->from($this->table, 'u') + ->leftJoin('u', 'preferences', 'p', $query->expr()->andX( + $query->expr()->eq('userid', 'uid'), + $query->expr()->eq('appid', $query->expr()->literal('settings')), + $query->expr()->eq('configkey', $query->expr()->literal('email'))) + ) + // sqlite doesn't like re-using a single named parameter here + ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) + ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) + ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%'))) + ->orderBy($query->func()->lower('displayname'), 'ASC') + ->orderBy('uid_lower', 'ASC') + ->setMaxResults($limit) + ->setFirstResult($offset); + + $result = $query->execute(); + $displayNames = []; + while ($row = $result->fetch()) { + $displayNames[(string)$row['uid']] = (string)$row['displayname']; + } + + return $displayNames; + } + + /** + * Check if the password is correct + * + * @param string $loginName The loginname + * @param string $password The password + * @return string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword(string $loginName, string $password) { + $this->fixDI(); + + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('uid', 'password') + ->from($this->table) + ->where( + $qb->expr()->eq( + 'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName)) + ) + ); + $result = $qb->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $storedHash = $row['password']; + $newHash = ''; + if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) { + if (!empty($newHash)) { + $this->updatePassword($loginName, $newHash); + } + return (string)$row['uid']; + } + } + + return false; + } + + /** + * Load an user in the cache + * + * @param string $uid the username + * @return boolean true if user was found, false otherwise + */ + private function loadUser($uid) { + $this->fixDI(); + + $uid = (string)$uid; + if (!isset($this->cache[$uid])) { + //guests $uid could be NULL or '' + if ($uid === '') { + $this->cache[$uid] = false; + return true; + } + + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('uid', 'displayname') + ->from($this->table) + ->where( + $qb->expr()->eq( + 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid)) + ) + ); + $result = $qb->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + $this->cache[$uid] = false; + + // "uid" is primary key, so there can only be a single result + if ($row !== false) { + $this->cache[$uid]['uid'] = (string)$row['uid']; + $this->cache[$uid]['displayname'] = (string)$row['displayname']; + } else { + return false; + } + } + + return true; + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = null, $offset = null) { + $limit = $this->fixLimit($limit); + + $users = $this->getDisplayNames($search, $limit, $offset); + $userIds = array_map(function ($uid) { + return (string)$uid; + }, array_keys($users)); + sort($userIds, SORT_STRING | SORT_FLAG_CASE); + return $userIds; + } + + /** + * check if a user exists + * + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + $this->loadUser($uid); + return $this->cache[$uid] !== false; + } + + /** + * get the user's home directory + * + * @param string $uid the username + * @return string|false + */ + public function getHome(string $uid) { + if ($this->userExists($uid)) { + return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid; + } + + return false; + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in the database + * + * @return int|bool + */ + public function countUsers() { + $this->fixDI(); + + $query = $this->dbConn->getQueryBuilder(); + $query->select($query->func()->count('uid')) + ->from($this->table); + $result = $query->execute(); + + return $result->fetchColumn(); + } + + /** + * returns the username for the given login name in the correct casing + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + if ($this->userExists($loginName)) { + return $this->cache[$loginName]['uid']; + } + + return false; + } + + /** + * Backend name to be shown in user management + * + * @return string the name of the backend to be shown + */ + public function getBackendName() { + return 'Database'; + } + + public static function preLoginNameUsedAsUserName($param) { + if (!isset($param['uid'])) { + throw new \Exception('key uid is expected to be set in $param'); + } + + $backends = \OC::$server->getUserManager()->getBackends(); + foreach ($backends as $backend) { + if ($backend instanceof Database) { + /** @var \OC\User\Database $backend */ + $uid = $backend->loginName2UserName($param['uid']); + if ($uid !== false) { + $param['uid'] = $uid; + return; + } + } + } + } + + public function getRealUID(string $uid): string { + if (!$this->userExists($uid)) { + throw new \RuntimeException($uid . ' does not exist'); + } + + return $this->cache[$uid]['uid']; + } + + private function fixLimit($limit) { + if (is_int($limit) && $limit >= 0) { + return $limit; + } + + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/LoginException.php b/docker/overlays/nextcloud/html/lib/private/User/LoginException.php new file mode 100644 index 0000000..77e70d0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/LoginException.php @@ -0,0 +1,27 @@ + + * @author Robin Appelman + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +class LoginException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/Manager.php b/docker/overlays/nextcloud/html/lib/private/User/Manager.php new file mode 100644 index 0000000..303050a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/Manager.php @@ -0,0 +1,647 @@ + + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Chan + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +use OC\Hooks\PublicEmitter; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IUser; +use OCP\IUserBackend; +use OCP\IUserManager; +use OCP\User\Backend\IGetRealUIDBackend; +use OCP\User\Events\CreateUserEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\UserInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Class Manager + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user, string $password) + * - change(\OC\User\User $user) + * - assignedUserId(string $uid) + * - preUnassignedUserId(string $uid) + * - postUnassignedUserId(string $uid) + * + * @package OC\User + */ +class Manager extends PublicEmitter implements IUserManager { + /** + * @var \OCP\UserInterface[] $backends + */ + private $backends = []; + + /** + * @var \OC\User\User[] $cachedUsers + */ + private $cachedUsers = []; + + /** @var IConfig */ + private $config; + + /** @var EventDispatcherInterface */ + private $dispatcher; + + /** @var IEventDispatcher */ + private $eventDispatcher; + + public function __construct(IConfig $config, + EventDispatcherInterface $oldDispatcher, + IEventDispatcher $eventDispatcher) { + $this->config = $config; + $this->dispatcher = $oldDispatcher; + $cachedUsers = &$this->cachedUsers; + $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) { + /** @var \OC\User\User $user */ + unset($cachedUsers[$user->getUID()]); + }); + $this->eventDispatcher = $eventDispatcher; + } + + /** + * Get the active backends + * @return \OCP\UserInterface[] + */ + public function getBackends() { + return $this->backends; + } + + /** + * register a user backend + * + * @param \OCP\UserInterface $backend + */ + public function registerBackend($backend) { + $this->backends[] = $backend; + } + + /** + * remove a user backend + * + * @param \OCP\UserInterface $backend + */ + public function removeBackend($backend) { + $this->cachedUsers = []; + if (($i = array_search($backend, $this->backends)) !== false) { + unset($this->backends[$i]); + } + } + + /** + * remove all user backends + */ + public function clearBackends() { + $this->cachedUsers = []; + $this->backends = []; + } + + /** + * get a user by user id + * + * @param string $uid + * @return \OC\User\User|null Either the user or null if the specified user does not exist + */ + public function get($uid) { + if (is_null($uid) || $uid === '' || $uid === false) { + return null; + } + if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends + return $this->cachedUsers[$uid]; + } + foreach ($this->backends as $backend) { + if ($backend->userExists($uid)) { + return $this->getUserObject($uid, $backend); + } + } + return null; + } + + /** + * get or construct the user object + * + * @param string $uid + * @param \OCP\UserInterface $backend + * @param bool $cacheUser If false the newly created user object will not be cached + * @return \OC\User\User + */ + protected function getUserObject($uid, $backend, $cacheUser = true) { + if ($backend instanceof IGetRealUIDBackend) { + $uid = $backend->getRealUID($uid); + } + + if (isset($this->cachedUsers[$uid])) { + return $this->cachedUsers[$uid]; + } + + $user = new User($uid, $backend, $this->dispatcher, $this, $this->config); + if ($cacheUser) { + $this->cachedUsers[$uid] = $user; + } + return $user; + } + + /** + * check if a user exists + * + * @param string $uid + * @return bool + */ + public function userExists($uid) { + $user = $this->get($uid); + return ($user !== null); + } + + /** + * Check if the password is valid for the user + * + * @param string $loginName + * @param string $password + * @return mixed the User object on success, false otherwise + */ + public function checkPassword($loginName, $password) { + $result = $this->checkPasswordNoLogging($loginName, $password); + + if ($result === false) { + \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']); + } + + return $result; + } + + /** + * Check if the password is valid for the user + * + * @internal + * @param string $loginName + * @param string $password + * @return IUser|false the User object on success, false otherwise + */ + public function checkPasswordNoLogging($loginName, $password) { + $loginName = str_replace("\0", '', $loginName); + $password = str_replace("\0", '', $password); + + foreach ($this->backends as $backend) { + if ($backend->implementsActions(Backend::CHECK_PASSWORD)) { + $uid = $backend->checkPassword($loginName, $password); + if ($uid !== false) { + return $this->getUserObject($uid, $backend); + } + } + } + + return false; + } + + /** + * search by user id + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function search($pattern, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $backendUsers = $backend->getUsers($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid) { + $users[$uid] = $this->getUserObject($uid, $backend); + } + } + } + + uasort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcasecmp($a->getUID(), $b->getUID()); + }); + return $users; + } + + /** + * search by displayName + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchDisplayName($pattern, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid => $displayName) { + $users[] = $this->getUserObject($uid, $backend); + } + } + } + + usort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcasecmp($a->getDisplayName(), $b->getDisplayName()); + }); + return $users; + } + + /** + * @param string $uid + * @param string $password + * @throws \InvalidArgumentException + * @return bool|IUser the created user or false + */ + public function createUser($uid, $password) { + $localBackends = []; + foreach ($this->backends as $backend) { + if ($backend instanceof Database) { + // First check if there is another user backend + $localBackends[] = $backend; + continue; + } + + if ($backend->implementsActions(Backend::CREATE_USER)) { + return $this->createUserFromBackend($uid, $password, $backend); + } + } + + foreach ($localBackends as $backend) { + if ($backend->implementsActions(Backend::CREATE_USER)) { + return $this->createUserFromBackend($uid, $password, $backend); + } + } + + return false; + } + + /** + * @param string $uid + * @param string $password + * @param UserInterface $backend + * @return IUser|null + * @throws \InvalidArgumentException + */ + public function createUserFromBackend($uid, $password, UserInterface $backend) { + $l = \OC::$server->getL10N('lib'); + + // Check the name for bad characters + // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'" + if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) { + throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:' + . ' "a-z", "A-Z", "0-9", and "_.@-\'"')); + } + + // No empty username + if (trim($uid) === '') { + throw new \InvalidArgumentException($l->t('A valid username must be provided')); + } + + // No whitespace at the beginning or at the end + if (trim($uid) !== $uid) { + throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end')); + } + + // Username only consists of 1 or 2 dots (directory traversal) + if ($uid === '.' || $uid === '..') { + throw new \InvalidArgumentException($l->t('Username must not consist of dots only')); + } + + if (!$this->verifyUid($uid)) { + throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user')); + } + + // No empty password + if (trim($password) === '') { + throw new \InvalidArgumentException($l->t('A valid password must be provided')); + } + + // Check if user already exists + if ($this->userExists($uid)) { + throw new \InvalidArgumentException($l->t('The username is already being used')); + } + + $this->emit('\OC\User', 'preCreateUser', [$uid, $password]); + $this->eventDispatcher->dispatchTyped(new CreateUserEvent($uid, $password)); + $state = $backend->createUser($uid, $password); + if ($state === false) { + throw new \InvalidArgumentException($l->t('Could not create user')); + } + $user = $this->getUserObject($uid, $backend); + if ($user instanceof IUser) { + $this->emit('\OC\User', 'postCreateUser', [$user, $password]); + $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); + } + return $user; + } + + /** + * returns how many users per backend exist (if supported by backend) + * + * @param boolean $hasLoggedIn when true only users that have a lastLogin + * entry in the preferences table will be affected + * @return array|int an array of backend class as key and count number as value + * if $hasLoggedIn is true only an int is returned + */ + public function countUsers($hasLoggedIn = false) { + if ($hasLoggedIn) { + return $this->countSeenUsers(); + } + $userCountStatistics = []; + foreach ($this->backends as $backend) { + if ($backend->implementsActions(Backend::COUNT_USERS)) { + $backendUsers = $backend->countUsers(); + if ($backendUsers !== false) { + if ($backend instanceof IUserBackend) { + $name = $backend->getBackendName(); + } else { + $name = get_class($backend); + } + if (isset($userCountStatistics[$name])) { + $userCountStatistics[$name] += $backendUsers; + } else { + $userCountStatistics[$name] = $backendUsers; + } + } + } + } + return $userCountStatistics; + } + + /** + * returns how many users per backend exist in the requested groups (if supported by backend) + * + * @param IGroup[] $groups an array of gid to search in + * @return array|int an array of backend class as key and count number as value + * if $hasLoggedIn is true only an int is returned + */ + public function countUsersOfGroups(array $groups) { + $users = []; + foreach ($groups as $group) { + $usersIds = array_map(function ($user) { + return $user->getUID(); + }, $group->getUsers()); + $users = array_merge($users, $usersIds); + } + return count(array_unique($users)); + } + + /** + * The callback is executed for each user on each backend. + * If the callback returns false no further users will be retrieved. + * + * @param \Closure $callback + * @param string $search + * @param boolean $onlySeen when true only users that have a lastLogin entry + * in the preferences table will be affected + * @since 9.0.0 + */ + public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) { + if ($onlySeen) { + $this->callForSeenUsers($callback); + } else { + foreach ($this->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers($search, $limit, $offset); + foreach ($users as $uid) { + if (!$backend->userExists($uid)) { + continue; + } + $user = $this->getUserObject($uid, $backend, false); + $return = $callback($user); + if ($return === false) { + break; + } + } + $offset += $limit; + } while (count($users) >= $limit); + } + } + } + + /** + * returns how many users are disabled + * + * @return int + * @since 12.0.0 + */ + public function countDisabledUsers(): int { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->func()->count('*')) + ->from('preferences') + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled'))) + ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR)); + + + $result = $queryBuilder->execute(); + $count = $result->fetchColumn(); + $result->closeCursor(); + + if ($count !== false) { + $count = (int)$count; + } else { + $count = 0; + } + + return $count; + } + + /** + * returns how many users are disabled in the requested groups + * + * @param array $groups groupids to search + * @return int + * @since 14.0.0 + */ + public function countDisabledUsersOfGroups(array $groups): int { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')')) + ->from('preferences', 'p') + ->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid')) + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled'))) + ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR)) + ->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY))); + + $result = $queryBuilder->execute(); + $count = $result->fetchColumn(); + $result->closeCursor(); + + if ($count !== false) { + $count = (int)$count; + } else { + $count = 0; + } + + return $count; + } + + /** + * returns how many users have logged in once + * + * @return int + * @since 11.0.0 + */ + public function countSeenUsers() { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->func()->count('*')) + ->from('preferences') + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin'))) + ->andWhere($queryBuilder->expr()->isNotNull('configvalue')); + + $query = $queryBuilder->execute(); + + $result = (int)$query->fetchColumn(); + $query->closeCursor(); + + return $result; + } + + /** + * @param \Closure $callback + * @since 11.0.0 + */ + public function callForSeenUsers(\Closure $callback) { + $limit = 1000; + $offset = 0; + do { + $userIds = $this->getSeenUserIds($limit, $offset); + $offset += $limit; + foreach ($userIds as $userId) { + foreach ($this->backends as $backend) { + if ($backend->userExists($userId)) { + $user = $this->getUserObject($userId, $backend, false); + $return = $callback($user); + if ($return === false) { + return; + } + break; + } + } + } + } while (count($userIds) >= $limit); + } + + /** + * Getting all userIds that have a listLogin value requires checking the + * value in php because on oracle you cannot use a clob in a where clause, + * preventing us from doing a not null or length(value) > 0 check. + * + * @param int $limit + * @param int $offset + * @return string[] with user ids + */ + private function getSeenUserIds($limit = null, $offset = null) { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select(['userid']) + ->from('preferences') + ->where($queryBuilder->expr()->eq( + 'appid', $queryBuilder->createNamedParameter('login')) + ) + ->andWhere($queryBuilder->expr()->eq( + 'configkey', $queryBuilder->createNamedParameter('lastLogin')) + ) + ->andWhere($queryBuilder->expr()->isNotNull('configvalue') + ); + + if ($limit !== null) { + $queryBuilder->setMaxResults($limit); + } + if ($offset !== null) { + $queryBuilder->setFirstResult($offset); + } + $query = $queryBuilder->execute(); + $result = []; + + while ($row = $query->fetch()) { + $result[] = $row['userid']; + } + + $query->closeCursor(); + + return $result; + } + + /** + * @param string $email + * @return IUser[] + * @since 9.1.0 + */ + public function getByEmail($email) { + $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email); + + $users = array_map(function ($uid) { + return $this->get($uid); + }, $userIds); + + return array_values(array_filter($users, function ($u) { + return ($u instanceof IUser); + })); + } + + private function verifyUid(string $uid): bool { + $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid'); + + if (\in_array($uid, [ + '.htaccess', + 'files_external', + '.ocdata', + 'owncloud.log', + 'nextcloud.log', + $appdata], true)) { + return false; + } + + $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data'); + + return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/NoUserException.php b/docker/overlays/nextcloud/html/lib/private/User/NoUserException.php new file mode 100644 index 0000000..57bb471 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/NoUserException.php @@ -0,0 +1,27 @@ + + * @author Jörn Friedrich Dreyer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +class NoUserException extends \Exception { +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/Session.php b/docker/overlays/nextcloud/html/lib/private/User/Session.php new file mode 100644 index 0000000..1f2eaad --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/Session.php @@ -0,0 +1,1016 @@ + + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Arthur Schiwon + * @author Bernhard Posselt + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Felix Rupp + * @author Greta Doci + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lionel Elie Mamane + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sandro Lutz + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +use OC; +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; +use OC\Authentication\Token\IProvider; +use OC\Authentication\Token\IToken; +use OC\Hooks\Emitter; +use OC\Hooks\PublicEmitter; +use OC_User; +use OC_Util; +use OCA\DAV\Connector\Sabre\Auth; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\NotPermittedException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IRequest; +use OCP\ISession; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Lockdown\ILockdownManager; +use OCP\Security\ISecureRandom; +use OCP\Session\Exceptions\SessionNotAvailableException; +use OCP\User\Events\PostLoginEvent; +use OCP\Util; +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Class Session + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user) + * - assignedUserId(string $uid) + * - preUnassignedUserId(string $uid) + * - postUnassignedUserId(string $uid) + * - preLogin(string $user, string $password) + * - postLogin(\OC\User\User $user, string $loginName, string $password, boolean $isTokenLogin) + * - preRememberedLogin(string $uid) + * - postRememberedLogin(\OC\User\User $user) + * - logout() + * - postLogout() + * + * @package OC\User + */ +class Session implements IUserSession, Emitter { + + /** @var Manager|PublicEmitter $manager */ + private $manager; + + /** @var ISession $session */ + private $session; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var IProvider */ + private $tokenProvider; + + /** @var IConfig */ + private $config; + + /** @var User $activeUser */ + protected $activeUser; + + /** @var ISecureRandom */ + private $random; + + /** @var ILockdownManager */ + private $lockdownManager; + + /** @var ILogger */ + private $logger; + /** @var IEventDispatcher */ + private $dispatcher; + + /** + * @param Manager $manager + * @param ISession $session + * @param ITimeFactory $timeFactory + * @param IProvider $tokenProvider + * @param IConfig $config + * @param ISecureRandom $random + * @param ILockdownManager $lockdownManager + * @param ILogger $logger + */ + public function __construct(Manager $manager, + ISession $session, + ITimeFactory $timeFactory, + $tokenProvider, + IConfig $config, + ISecureRandom $random, + ILockdownManager $lockdownManager, + ILogger $logger, + IEventDispatcher $dispatcher + ) { + $this->manager = $manager; + $this->session = $session; + $this->timeFactory = $timeFactory; + $this->tokenProvider = $tokenProvider; + $this->config = $config; + $this->random = $random; + $this->lockdownManager = $lockdownManager; + $this->logger = $logger; + $this->dispatcher = $dispatcher; + } + + /** + * @param IProvider $provider + */ + public function setTokenProvider(IProvider $provider) { + $this->tokenProvider = $provider; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + $this->manager->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->manager->removeListener($scope, $method, $callback); + } + + /** + * get the manager object + * + * @return Manager|PublicEmitter + */ + public function getManager() { + return $this->manager; + } + + /** + * get the session object + * + * @return ISession + */ + public function getSession() { + return $this->session; + } + + /** + * set the session object + * + * @param ISession $session + */ + public function setSession(ISession $session) { + if ($this->session instanceof ISession) { + $this->session->close(); + } + $this->session = $session; + $this->activeUser = null; + } + + /** + * set the currently active user + * + * @param IUser|null $user + */ + public function setUser($user) { + if (is_null($user)) { + $this->session->remove('user_id'); + } else { + $this->session->set('user_id', $user->getUID()); + } + $this->activeUser = $user; + } + + /** + * get the current active user + * + * @return IUser|null Current user, otherwise null + */ + public function getUser() { + // FIXME: This is a quick'n dirty work-around for the incognito mode as + // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155 + if (OC_User::isIncognitoMode()) { + return null; + } + if (is_null($this->activeUser)) { + $uid = $this->session->get('user_id'); + if (is_null($uid)) { + return null; + } + $this->activeUser = $this->manager->get($uid); + if (is_null($this->activeUser)) { + return null; + } + $this->validateSession(); + } + return $this->activeUser; + } + + /** + * Validate whether the current session is valid + * + * - For token-authenticated clients, the token validity is checked + * - For browsers, the session token validity is checked + */ + protected function validateSession() { + $token = null; + $appPassword = $this->session->get('app_password'); + + if (is_null($appPassword)) { + try { + $token = $this->session->getId(); + } catch (SessionNotAvailableException $ex) { + return; + } + } else { + $token = $appPassword; + } + + if (!$this->validateToken($token)) { + // Session was invalidated + $this->logout(); + } + } + + /** + * Checks whether the user is logged in + * + * @return bool if logged in + */ + public function isLoggedIn() { + $user = $this->getUser(); + if (is_null($user)) { + return false; + } + + return $user->isEnabled(); + } + + /** + * set the login name + * + * @param string|null $loginName for the logged in user + */ + public function setLoginName($loginName) { + if (is_null($loginName)) { + $this->session->remove('loginname'); + } else { + $this->session->set('loginname', $loginName); + } + } + + /** + * get the login name of the current user + * + * @return string + */ + public function getLoginName() { + if ($this->activeUser) { + return $this->session->get('loginname'); + } + + $uid = $this->session->get('user_id'); + if ($uid) { + $this->activeUser = $this->manager->get($uid); + return $this->session->get('loginname'); + } + + return null; + } + + /** + * @return null|string + */ + public function getImpersonatingUserID(): ?string { + return $this->session->get('oldUserId'); + } + + public function setImpersonatingUserID(bool $useCurrentUser = true): void { + if ($useCurrentUser === false) { + $this->session->remove('oldUserId'); + return; + } + + $currentUser = $this->getUser(); + + if ($currentUser === null) { + throw new \OC\User\NoUserException(); + } + $this->session->set('oldUserId', $currentUser->getUID()); + } + /** + * set the token id + * + * @param int|null $token that was used to log in + */ + protected function setToken($token) { + if ($token === null) { + $this->session->remove('token-id'); + } else { + $this->session->set('token-id', $token); + } + } + + /** + * try to log in with the provided credentials + * + * @param string $uid + * @param string $password + * @return boolean|null + * @throws LoginException + */ + public function login($uid, $password) { + $this->session->regenerateId(); + if ($this->validateToken($password, $uid)) { + return $this->loginWithToken($password); + } + return $this->loginWithPassword($uid, $password); + } + + /** + * @param IUser $user + * @param array $loginDetails + * @param bool $regenerateSessionId + * @return true returns true if login successful or an exception otherwise + * @throws LoginException + */ + public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) { + if (!$user->isEnabled()) { + // disabled users can not log in + // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory + $message = \OC::$server->getL10N('lib')->t('User disabled'); + throw new LoginException($message); + } + + if ($regenerateSessionId) { + $this->session->regenerateId(); + } + + $this->setUser($user); + $this->setLoginName($loginDetails['loginName']); + + $isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken; + if ($isToken) { + $this->setToken($loginDetails['token']->getId()); + $this->lockdownManager->setToken($loginDetails['token']); + $firstTimeLogin = false; + } else { + $this->setToken(null); + $firstTimeLogin = $user->updateLastLoginTimestamp(); + } + + $this->dispatcher->dispatchTyped(new PostLoginEvent( + $user, + $loginDetails['loginName'], + $loginDetails['password'], + $isToken + )); + $this->manager->emit('\OC\User', 'postLogin', [ + $user, + $loginDetails['loginName'], + $loginDetails['password'], + $isToken, + ]); + if ($this->isLoggedIn()) { + $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); + return true; + } + + $message = \OC::$server->getL10N('lib')->t('Login canceled by app'); + throw new LoginException($message); + } + + /** + * Tries to log in a client + * + * Checks token auth enforced + * Checks 2FA enabled + * + * @param string $user + * @param string $password + * @param IRequest $request + * @param OC\Security\Bruteforce\Throttler $throttler + * @throws LoginException + * @throws PasswordLoginForbiddenException + * @return boolean + */ + public function logClientIn($user, + $password, + IRequest $request, + OC\Security\Bruteforce\Throttler $throttler) { + $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login'); + + if ($this->manager instanceof PublicEmitter) { + $this->manager->emit('\OC\User', 'preLogin', [$user, $password]); + } + + try { + $isTokenPassword = $this->isTokenPassword($password); + } catch (ExpiredTokenException $e) { + // Just return on an expired token no need to check further or record a failed login + return false; + } + + if (!$isTokenPassword && $this->isTokenAuthEnforced()) { + throw new PasswordLoginForbiddenException(); + } + if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) { + throw new PasswordLoginForbiddenException(); + } + + // Try to login with this username and password + if (!$this->login($user, $password)) { + + // Failed, maybe the user used their email address + $users = $this->manager->getByEmail($user); + if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) { + $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']); + + $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]); + + $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user)); + + if ($currentDelay === 0) { + $throttler->sleepDelay($request->getRemoteAddress(), 'login'); + } + return false; + } + } + + if ($isTokenPassword) { + $this->session->set('app_password', $password); + } elseif ($this->supportsCookies($request)) { + // Password login, but cookies supported -> create (browser) session token + $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); + } + + return true; + } + + protected function supportsCookies(IRequest $request) { + if (!is_null($request->getCookie('cookie_test'))) { + return true; + } + setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600); + return false; + } + + private function isTokenAuthEnforced() { + return $this->config->getSystemValue('token_auth_enforced', false); + } + + protected function isTwoFactorEnforced($username) { + Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + ['uid' => &$username] + ); + $user = $this->manager->get($username); + if (is_null($user)) { + $users = $this->manager->getByEmail($username); + if (empty($users)) { + return false; + } + if (count($users) !== 1) { + return true; + } + $user = $users[0]; + } + // DI not possible due to cyclic dependencies :'-/ + return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user); + } + + /** + * Check if the given 'password' is actually a device token + * + * @param string $password + * @return boolean + * @throws ExpiredTokenException + */ + public function isTokenPassword($password) { + try { + $this->tokenProvider->getToken($password); + return true; + } catch (ExpiredTokenException $e) { + throw $e; + } catch (InvalidTokenException $ex) { + $this->logger->logException($ex, [ + 'level' => ILogger::DEBUG, + 'message' => 'Token is not valid: ' . $ex->getMessage(), + ]); + return false; + } + } + + protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) { + if ($refreshCsrfToken) { + // TODO: mock/inject/use non-static + // Refresh the token + \OC::$server->getCsrfTokenManager()->refreshToken(); + } + + //we need to pass the user name, which may differ from login name + $user = $this->getUser()->getUID(); + OC_Util::setupFS($user); + + if ($firstTimeLogin) { + // TODO: lock necessary? + //trigger creation of user home and /files folder + $userFolder = \OC::$server->getUserFolder($user); + + try { + // copy skeleton + \OC_Util::copySkeleton($user, $userFolder); + } catch (NotPermittedException $ex) { + // read only uses + } + + // trigger any other initialization + \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser())); + } + } + + /** + * Tries to login the user with HTTP Basic Authentication + * + * @todo do not allow basic auth if the user is 2FA enforced + * @param IRequest $request + * @param OC\Security\Bruteforce\Throttler $throttler + * @return boolean if the login was successful + */ + public function tryBasicAuthLogin(IRequest $request, + OC\Security\Bruteforce\Throttler $throttler) { + if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { + try { + if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) { + /** + * Add DAV authenticated. This should in an ideal world not be + * necessary but the iOS App reads cookies from anywhere instead + * only the DAV endpoint. + * This makes sure that the cookies will be valid for the whole scope + * @see https://github.com/owncloud/core/issues/22893 + */ + $this->session->set( + Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() + ); + + // Set the last-password-confirm session to make the sudo mode work + $this->session->set('last-password-confirm', $this->timeFactory->getTime()); + + return true; + } + } catch (PasswordLoginForbiddenException $ex) { + // Nothing to do + } + } + return false; + } + + /** + * Log an user in via login name and password + * + * @param string $uid + * @param string $password + * @return boolean + * @throws LoginException if an app canceld the login process or the user is not enabled + */ + private function loginWithPassword($uid, $password) { + $user = $this->manager->checkPasswordNoLogging($uid, $password); + if ($user === false) { + // Password check failed + return false; + } + + return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false); + } + + /** + * Log an user in with a given token (id) + * + * @param string $token + * @return boolean + * @throws LoginException if an app canceled the login process or the user is not enabled + */ + private function loginWithToken($token) { + try { + $dbToken = $this->tokenProvider->getToken($token); + } catch (InvalidTokenException $ex) { + return false; + } + $uid = $dbToken->getUID(); + + // When logging in with token, the password must be decrypted first before passing to login hook + $password = ''; + try { + $password = $this->tokenProvider->getPassword($dbToken, $token); + } catch (PasswordlessTokenException $ex) { + // Ignore and use empty string instead + } + + $this->manager->emit('\OC\User', 'preLogin', [$uid, $password]); + + $user = $this->manager->get($uid); + if (is_null($user)) { + // user does not exist + return false; + } + + return $this->completeLogin( + $user, + [ + 'loginName' => $dbToken->getLoginName(), + 'password' => $password, + 'token' => $dbToken + ], + false); + } + + /** + * Create a new session token for the given user credentials + * + * @param IRequest $request + * @param string $uid user UID + * @param string $loginName login name + * @param string $password + * @param int $remember + * @return boolean + */ + public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) { + if (is_null($this->manager->get($uid))) { + // User does not exist + return false; + } + $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser'; + try { + $sessionId = $this->session->getId(); + $pwd = $this->getPassword($password); + // Make sure the current sessionId has no leftover tokens + $this->tokenProvider->invalidateToken($sessionId); + $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember); + return true; + } catch (SessionNotAvailableException $ex) { + // This can happen with OCC, where a memory session is used + // if a memory session is used, we shouldn't create a session token anyway + return false; + } + } + + /** + * Checks if the given password is a token. + * If yes, the password is extracted from the token. + * If no, the same password is returned. + * + * @param string $password either the login password or a device token + * @return string|null the password or null if none was set in the token + */ + private function getPassword($password) { + if (is_null($password)) { + // This is surely no token ;-) + return null; + } + try { + $token = $this->tokenProvider->getToken($password); + try { + return $this->tokenProvider->getPassword($token, $password); + } catch (PasswordlessTokenException $ex) { + return null; + } + } catch (InvalidTokenException $ex) { + return $password; + } + } + + /** + * @param IToken $dbToken + * @param string $token + * @return boolean + */ + private function checkTokenCredentials(IToken $dbToken, $token) { + // Check whether login credentials are still valid and the user was not disabled + // This check is performed each 5 minutes + $lastCheck = $dbToken->getLastCheck() ? : 0; + $now = $this->timeFactory->getTime(); + if ($lastCheck > ($now - 60 * 5)) { + // Checked performed recently, nothing to do now + return true; + } + + try { + $pwd = $this->tokenProvider->getPassword($dbToken, $token); + } catch (InvalidTokenException $ex) { + // An invalid token password was used -> log user out + return false; + } catch (PasswordlessTokenException $ex) { + // Token has no password + + if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { + $this->tokenProvider->invalidateToken($token); + return false; + } + + $dbToken->setLastCheck($now); + return true; + } + + // Invalidate token if the user is no longer active + if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { + $this->tokenProvider->invalidateToken($token); + return false; + } + + // If the token password is no longer valid mark it as such + if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) { + $this->tokenProvider->markPasswordInvalid($dbToken, $token); + // User is logged out + return false; + } + + $dbToken->setLastCheck($now); + return true; + } + + /** + * Check if the given token exists and performs password/user-enabled checks + * + * Invalidates the token if checks fail + * + * @param string $token + * @param string $user login name + * @return boolean + */ + private function validateToken($token, $user = null) { + try { + $dbToken = $this->tokenProvider->getToken($token); + } catch (InvalidTokenException $ex) { + return false; + } + + // Check if login names match + if (!is_null($user) && $dbToken->getLoginName() !== $user) { + // TODO: this makes it imposssible to use different login names on browser and client + // e.g. login by e-mail 'user@example.com' on browser for generating the token will not + // allow to use the client token with the login name 'user'. + return false; + } + + if (!$this->checkTokenCredentials($dbToken, $token)) { + return false; + } + + // Update token scope + $this->lockdownManager->setToken($dbToken); + + $this->tokenProvider->updateTokenActivity($dbToken); + + return true; + } + + /** + * Tries to login the user with auth token header + * + * @param IRequest $request + * @todo check remember me cookie + * @return boolean + */ + public function tryTokenLogin(IRequest $request) { + $authHeader = $request->getHeader('Authorization'); + if (strpos($authHeader, 'Bearer ') === false) { + // No auth header, let's try session id + try { + $token = $this->session->getId(); + } catch (SessionNotAvailableException $ex) { + return false; + } + } else { + $token = substr($authHeader, 7); + } + + if (!$this->loginWithToken($token)) { + return false; + } + if (!$this->validateToken($token)) { + return false; + } + + // Set the session variable so we know this is an app password + $this->session->set('app_password', $token); + + return true; + } + + /** + * perform login using the magic cookie (remember login) + * + * @param string $uid the username + * @param string $currentToken + * @param string $oldSessionId + * @return bool + */ + public function loginWithCookie($uid, $currentToken, $oldSessionId) { + $this->session->regenerateId(); + $this->manager->emit('\OC\User', 'preRememberedLogin', [$uid]); + $user = $this->manager->get($uid); + if (is_null($user)) { + // user does not exist + return false; + } + + // get stored tokens + $tokens = $this->config->getUserKeys($uid, 'login_token'); + // test cookies token against stored tokens + if (!in_array($currentToken, $tokens, true)) { + return false; + } + // replace successfully used token with a new one + $this->config->deleteUserValue($uid, 'login_token', $currentToken); + $newToken = $this->random->generate(32); + $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime()); + + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId); + } catch (SessionNotAvailableException $ex) { + return false; + } catch (InvalidTokenException $ex) { + \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']); + return false; + } + + $this->setMagicInCookie($user->getUID(), $newToken); + + //login + $this->setUser($user); + $this->setLoginName($token->getLoginName()); + $this->setToken($token->getId()); + $this->lockdownManager->setToken($token); + $user->updateLastLoginTimestamp(); + $password = null; + try { + $password = $this->tokenProvider->getPassword($token, $sessionId); + } catch (PasswordlessTokenException $ex) { + // Ignore + } + $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]); + return true; + } + + /** + * @param IUser $user + */ + public function createRememberMeToken(IUser $user) { + $token = $this->random->generate(32); + $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime()); + $this->setMagicInCookie($user->getUID(), $token); + } + + /** + * logout the user from the session + */ + public function logout() { + $user = $this->getUser(); + $this->manager->emit('\OC\User', 'logout', [$user]); + if ($user !== null) { + try { + $this->tokenProvider->invalidateToken($this->session->getId()); + } catch (SessionNotAvailableException $ex) { + } + } + $this->setUser(null); + $this->setLoginName(null); + $this->setToken(null); + $this->unsetMagicInCookie(); + $this->session->clear(); + $this->manager->emit('\OC\User', 'postLogout', [$user]); + } + + /** + * Set cookie value to use in next page load + * + * @param string $username username to be set + * @param string $token + */ + public function setMagicInCookie($username, $token) { + $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; + $webRoot = \OC::$WEBROOT; + if ($webRoot === '') { + $webRoot = '/'; + } + + $maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + \OC\Http\CookieHelper::setCookie( + 'nc_username', + $username, + $maxAge, + $webRoot, + '', + $secureCookie, + true, + \OC\Http\CookieHelper::SAMESITE_LAX + ); + \OC\Http\CookieHelper::setCookie( + 'nc_token', + $token, + $maxAge, + $webRoot, + '', + $secureCookie, + true, + \OC\Http\CookieHelper::SAMESITE_LAX + ); + try { + \OC\Http\CookieHelper::setCookie( + 'nc_session_id', + $this->session->getId(), + $maxAge, + $webRoot, + '', + $secureCookie, + true, + \OC\Http\CookieHelper::SAMESITE_LAX + ); + } catch (SessionNotAvailableException $ex) { + // ignore + } + } + + /** + * Remove cookie for "remember username" + */ + public function unsetMagicInCookie() { + //TODO: DI for cookies and IRequest + $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; + + unset($_COOKIE['nc_username']); //TODO: DI + unset($_COOKIE['nc_token']); + unset($_COOKIE['nc_session_id']); + setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); + setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); + setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); + // old cookies might be stored under /webroot/ instead of /webroot + // and Firefox doesn't like it! + setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); + setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); + setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); + } + + /** + * Update password of the browser session token if there is one + * + * @param string $password + */ + public function updateSessionTokenPassword($password) { + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + $this->tokenProvider->setPassword($token, $sessionId, $password); + } catch (SessionNotAvailableException $ex) { + // Nothing to do + } catch (InvalidTokenException $ex) { + // Nothing to do + } + } + + public function updateTokens(string $uid, string $password) { + $this->tokenProvider->updatePasswords($uid, $password); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/User/User.php b/docker/overlays/nextcloud/html/lib/private/User/User.php new file mode 100644 index 0000000..365b8ae --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/User/User.php @@ -0,0 +1,503 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Leon Klingele + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +use OC\Accounts\AccountManager; +use OC\Avatar\AvatarManager; +use OC\Files\Cache\Storage; +use OC\Hooks\Emitter; +use OC_Helper; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\BeforeUserRemovedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\IAvatarManager; +use OCP\IConfig; +use OCP\IImage; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserBackend; +use OCP\User\GetQuotaEvent; +use OCP\UserInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class User implements IUser { + /** @var string */ + private $uid; + + /** @var string */ + private $displayName; + + /** @var UserInterface|null */ + private $backend; + /** @var EventDispatcherInterface */ + private $legacyDispatcher; + + /** @var IEventDispatcher */ + private $dispatcher; + + /** @var bool */ + private $enabled; + + /** @var Emitter|Manager */ + private $emitter; + + /** @var string */ + private $home; + + /** @var int */ + private $lastLogin; + + /** @var \OCP\IConfig */ + private $config; + + /** @var IAvatarManager */ + private $avatarManager; + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(string $uid, ?UserInterface $backend, EventDispatcherInterface $dispatcher, $emitter = null, IConfig $config = null, $urlGenerator = null) { + $this->uid = $uid; + $this->backend = $backend; + $this->legacyDispatcher = $dispatcher; + $this->emitter = $emitter; + if (is_null($config)) { + $config = \OC::$server->getConfig(); + } + $this->config = $config; + $this->urlGenerator = $urlGenerator; + $enabled = $this->config->getUserValue($uid, 'core', 'enabled', 'true'); + $this->enabled = ($enabled === 'true'); + $this->lastLogin = $this->config->getUserValue($uid, 'login', 'lastLogin', 0); + if (is_null($this->urlGenerator)) { + $this->urlGenerator = \OC::$server->getURLGenerator(); + } + // TODO: inject + $this->dispatcher = \OC::$server->query(IEventDispatcher::class); + } + + /** + * get the user id + * + * @return string + */ + public function getUID() { + return $this->uid; + } + + /** + * get the display name for the user, if no specific display name is set it will fallback to the user id + * + * @return string + */ + public function getDisplayName() { + if (!isset($this->displayName)) { + $displayName = ''; + if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) { + // get display name and strip whitespace from the beginning and end of it + $backendDisplayName = $this->backend->getDisplayName($this->uid); + if (is_string($backendDisplayName)) { + $displayName = trim($backendDisplayName); + } + } + + if (!empty($displayName)) { + $this->displayName = $displayName; + } else { + $this->displayName = $this->uid; + } + } + return $this->displayName; + } + + /** + * set the displayname for the user + * + * @param string $displayName + * @return bool + */ + public function setDisplayName($displayName) { + $displayName = trim($displayName); + $oldDisplayName = $this->getDisplayName(); + if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) { + $result = $this->backend->setDisplayName($this->uid, $displayName); + if ($result) { + $this->displayName = $displayName; + $this->triggerChange('displayName', $displayName, $oldDisplayName); + } + return $result !== false; + } + return false; + } + + /** + * set the email address of the user + * + * @param string|null $mailAddress + * @return void + * @since 9.0.0 + */ + public function setEMailAddress($mailAddress) { + $oldMailAddress = $this->getEMailAddress(); + if ($oldMailAddress !== $mailAddress) { + if ($mailAddress === '') { + $this->config->deleteUserValue($this->uid, 'settings', 'email'); + } else { + $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); + } + $this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress); + } + } + + /** + * returns the timestamp of the user's last login or 0 if the user did never + * login + * + * @return int + */ + public function getLastLogin() { + return $this->lastLogin; + } + + /** + * updates the timestamp of the most recent login of this user + */ + public function updateLastLoginTimestamp() { + $firstTimeLogin = ($this->lastLogin === 0); + $this->lastLogin = time(); + $this->config->setUserValue( + $this->uid, 'login', 'lastLogin', $this->lastLogin); + + return $firstTimeLogin; + } + + /** + * Delete the user + * + * @return bool + */ + public function delete() { + $this->legacyDispatcher->dispatch(IUser::class . '::preDelete', new GenericEvent($this)); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preDelete', [$this]); + } + // get the home now because it won't return it after user deletion + $homePath = $this->getHome(); + $result = $this->backend->deleteUser($this->uid); + if ($result) { + + // FIXME: Feels like an hack - suggestions? + + $groupManager = \OC::$server->getGroupManager(); + // We have to delete the user from all groups + foreach ($groupManager->getUserGroupIds($this) as $groupId) { + $group = $groupManager->get($groupId); + if ($group) { + $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this)); + $group->removeUser($this); + $this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this)); + } + } + // Delete the user's keys in preferences + \OC::$server->getConfig()->deleteAllUserValues($this->uid); + + // Delete user files in /data/ + if ($homePath !== false) { + // FIXME: this operates directly on FS, should use View instead... + // also this is not testable/mockable... + \OC_Helper::rmdirr($homePath); + } + + // Delete the users entry in the storage table + Storage::remove('home::' . $this->uid); + + \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid); + \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); + + /** @var IAvatarManager $avatarManager */ + $avatarManager = \OC::$server->query(AvatarManager::class); + $avatarManager->deleteUserAvatar($this->uid); + + $notification = \OC::$server->getNotificationManager()->createNotification(); + $notification->setUser($this->uid); + \OC::$server->getNotificationManager()->markProcessed($notification); + + /** @var AccountManager $accountManager */ + $accountManager = \OC::$server->query(AccountManager::class); + $accountManager->deleteUser($this); + + $this->legacyDispatcher->dispatch(IUser::class . '::postDelete', new GenericEvent($this)); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postDelete', [$this]); + } + } + return !($result === false); + } + + /** + * Set the password of the user + * + * @param string $password + * @param string $recoveryPassword for the encryption app to reset encryption keys + * @return bool + */ + public function setPassword($password, $recoveryPassword = null) { + $this->legacyDispatcher->dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [ + 'password' => $password, + 'recoveryPassword' => $recoveryPassword, + ])); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]); + } + if ($this->backend->implementsActions(Backend::SET_PASSWORD)) { + $result = $this->backend->setPassword($this->uid, $password); + $this->legacyDispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [ + 'password' => $password, + 'recoveryPassword' => $recoveryPassword, + ])); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]); + } + return !($result === false); + } else { + return false; + } + } + + /** + * get the users home folder to mount + * + * @return string + */ + public function getHome() { + if (!$this->home) { + if ($this->backend->implementsActions(Backend::GET_HOME) and $home = $this->backend->getHome($this->uid)) { + $this->home = $home; + } elseif ($this->config) { + $this->home = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid; + } else { + $this->home = \OC::$SERVERROOT . '/data/' . $this->uid; + } + } + return $this->home; + } + + /** + * Get the name of the backend class the user is connected with + * + * @return string + */ + public function getBackendClassName() { + if ($this->backend instanceof IUserBackend) { + return $this->backend->getBackendName(); + } + return get_class($this->backend); + } + + public function getBackend() { + return $this->backend; + } + + /** + * check if the backend allows the user to change his avatar on Personal page + * + * @return bool + */ + public function canChangeAvatar() { + if ($this->backend->implementsActions(Backend::PROVIDE_AVATAR)) { + return $this->backend->canChangeAvatar($this->uid); + } + return true; + } + + /** + * check if the backend supports changing passwords + * + * @return bool + */ + public function canChangePassword() { + return $this->backend->implementsActions(Backend::SET_PASSWORD); + } + + /** + * check if the backend supports changing display names + * + * @return bool + */ + public function canChangeDisplayName() { + if ($this->config->getSystemValue('allow_user_to_change_display_name') === false) { + return false; + } + return $this->backend->implementsActions(Backend::SET_DISPLAYNAME); + } + + /** + * check if the user is enabled + * + * @return bool + */ + public function isEnabled() { + return $this->enabled; + } + + /** + * set the enabled status for the user + * + * @param bool $enabled + */ + public function setEnabled(bool $enabled = true) { + $oldStatus = $this->isEnabled(); + $this->enabled = $enabled; + if ($oldStatus !== $this->enabled) { + // TODO: First change the value, then trigger the event as done for all other properties. + $this->triggerChange('enabled', $enabled, $oldStatus); + $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false'); + } + } + + /** + * get the users email address + * + * @return string|null + * @since 9.0.0 + */ + public function getEMailAddress() { + return $this->config->getUserValue($this->uid, 'settings', 'email', null); + } + + /** + * get the users' quota + * + * @return string + * @since 9.0.0 + */ + public function getQuota() { + // allow apps to modify the user quota by hooking into the event + $event = new GetQuotaEvent($this); + $this->dispatcher->dispatchTyped($event); + $overwriteQuota = $event->getQuota(); + if ($overwriteQuota) { + $quota = $overwriteQuota; + } else { + $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default'); + } + if ($quota === 'default') { + $quota = $this->config->getAppValue('files', 'default_quota', 'none'); + } + return $quota; + } + + /** + * set the users' quota + * + * @param string $quota + * @return void + * @since 9.0.0 + */ + public function setQuota($quota) { + $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', ''); + if ($quota !== 'none' and $quota !== 'default') { + $quota = OC_Helper::computerFileSize($quota); + $quota = OC_Helper::humanFileSize($quota); + } + if ($quota !== $oldQuota) { + $this->config->setUserValue($this->uid, 'files', 'quota', $quota); + $this->triggerChange('quota', $quota, $oldQuota); + } + } + + /** + * get the avatar image if it exists + * + * @param int $size + * @return IImage|null + * @since 9.0.0 + */ + public function getAvatarImage($size) { + // delay the initialization + if (is_null($this->avatarManager)) { + $this->avatarManager = \OC::$server->getAvatarManager(); + } + + $avatar = $this->avatarManager->getAvatar($this->uid); + $image = $avatar->get(-1); + if ($image) { + return $image; + } + + return null; + } + + /** + * get the federation cloud id + * + * @return string + * @since 9.0.0 + */ + public function getCloudId() { + $uid = $this->getUID(); + $server = $this->urlGenerator->getAbsoluteURL('/'); + $server = rtrim($this->removeProtocolFromUrl($server), '/'); + return \OC::$server->getCloudIdManager()->getCloudId($uid, $server)->getId(); + } + + /** + * @param string $url + * @return string + */ + private function removeProtocolFromUrl($url) { + if (strpos($url, 'https://') === 0) { + return substr($url, strlen('https://')); + } elseif (strpos($url, 'http://') === 0) { + return substr($url, strlen('http://')); + } + + return $url; + } + + public function triggerChange($feature, $value = null, $oldValue = null) { + $this->legacyDispatcher->dispatch(IUser::class . '::changeUser', new GenericEvent($this, [ + 'feature' => $feature, + 'value' => $value, + 'oldValue' => $oldValue, + ])); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/UserStatus/Manager.php b/docker/overlays/nextcloud/html/lib/private/UserStatus/Manager.php new file mode 100644 index 0000000..aeef8df --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/UserStatus/Manager.php @@ -0,0 +1,106 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\UserStatus; + +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\UserStatus\IManager; +use OCP\UserStatus\IProvider; +use Psr\Container\ContainerExceptionInterface; + +class Manager implements IManager { + + /** @var IServerContainer */ + private $container; + + /** @var ILogger */ + private $logger; + + /** @var null */ + private $providerClass; + + /** @var IProvider */ + private $provider; + + /** + * Manager constructor. + * + * @param IServerContainer $container + * @param ILogger $logger + */ + public function __construct(IServerContainer $container, + ILogger $logger) { + $this->container = $container; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function getUserStatuses(array $userIds): array { + $this->setupProvider(); + if (!$this->provider) { + return []; + } + + return $this->provider->getUserStatuses($userIds); + } + + /** + * @param string $class + * @since 20.0.0 + * @internal + */ + public function registerProvider(string $class): void { + $this->providerClass = $class; + $this->provider = null; + } + + /** + * Lazily set up provider + */ + private function setupProvider(): void { + if ($this->provider !== null) { + return; + } + if ($this->providerClass === null) { + return; + } + + try { + $provider = $this->container->get($this->providerClass); + } catch (ContainerExceptionInterface $e) { + $this->logger->logException($e, [ + 'message' => 'Could not load user-status provider dynamically: ' . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + return; + } + + $this->provider = $provider; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_API.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_API.php new file mode 100644 index 0000000..37a496a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_API.php @@ -0,0 +1,189 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Tom Needham + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use OCP\API; +use OCP\AppFramework\Http; + +class OC_API { + + /** + * api actions + */ + protected static $actions = []; + + /** + * respond to a call + * @param \OC\OCS\Result $result + * @param string $format the format xml|json + */ + public static function respond($result, $format='xml') { + $request = \OC::$server->getRequest(); + + // Send 401 headers if unauthorised + if ($result->getStatusCode() === API::RESPOND_UNAUTHORISED) { + // If request comes from JS return dummy auth request + if ($request->getHeader('X-Requested-With') === 'XMLHttpRequest') { + header('WWW-Authenticate: DummyBasic realm="Authorisation Required"'); + } else { + header('WWW-Authenticate: Basic realm="Authorisation Required"'); + } + http_response_code(401); + } + + foreach ($result->getHeaders() as $name => $value) { + header($name . ': ' . $value); + } + + $meta = $result->getMeta(); + $data = $result->getData(); + if (self::isV2($request)) { + $statusCode = self::mapStatusCodes($result->getStatusCode()); + if (!is_null($statusCode)) { + $meta['statuscode'] = $statusCode; + http_response_code($statusCode); + } + } + + self::setContentType($format); + $body = self::renderResult($format, $meta, $data); + echo $body; + } + + /** + * @param XMLWriter $writer + */ + private static function toXML($array, $writer) { + foreach ($array as $k => $v) { + if ($k[0] === '@') { + $writer->writeAttribute(substr($k, 1), $v); + continue; + } elseif (is_numeric($k)) { + $k = 'element'; + } + if (is_array($v)) { + $writer->startElement($k); + self::toXML($v, $writer); + $writer->endElement(); + } else { + $writer->writeElement($k, $v); + } + } + } + + /** + * @return string + */ + public static function requestedFormat() { + $formats = ['json', 'xml']; + + $format = !empty($_GET['format']) && in_array($_GET['format'], $formats) ? $_GET['format'] : 'xml'; + return $format; + } + + /** + * Based on the requested format the response content type is set + * @param string $format + */ + public static function setContentType($format = null) { + $format = is_null($format) ? self::requestedFormat() : $format; + if ($format === 'xml') { + header('Content-type: text/xml; charset=UTF-8'); + return; + } + + if ($format === 'json') { + header('Content-Type: application/json; charset=utf-8'); + return; + } + + header('Content-Type: application/octet-stream; charset=utf-8'); + } + + /** + * @param \OCP\IRequest $request + * @return bool + */ + protected static function isV2(\OCP\IRequest $request) { + $script = $request->getScriptName(); + + return substr($script, -11) === '/ocs/v2.php'; + } + + /** + * @param integer $sc + * @return int + */ + public static function mapStatusCodes($sc) { + switch ($sc) { + case API::RESPOND_NOT_FOUND: + return Http::STATUS_NOT_FOUND; + case API::RESPOND_SERVER_ERROR: + return Http::STATUS_INTERNAL_SERVER_ERROR; + case API::RESPOND_UNKNOWN_ERROR: + return Http::STATUS_INTERNAL_SERVER_ERROR; + case API::RESPOND_UNAUTHORISED: + // already handled for v1 + return null; + case 100: + return Http::STATUS_OK; + } + // any 2xx, 4xx and 5xx will be used as is + if ($sc >= 200 && $sc < 600) { + return $sc; + } + + return Http::STATUS_BAD_REQUEST; + } + + /** + * @param string $format + * @return string + */ + public static function renderResult($format, $meta, $data) { + $response = [ + 'ocs' => [ + 'meta' => $meta, + 'data' => $data, + ], + ]; + if ($format == 'json') { + return OC_JSON::encode($response); + } + + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + self::toXML($response, $writer); + $writer->endDocument(); + return $writer->outputMemory(true); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_App.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_App.php new file mode 100644 index 0000000..5851fc0 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_App.php @@ -0,0 +1,1180 @@ + + * + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bernhard Posselt + * @author Borjan Tchakaloff + * @author Brice Maron + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Daniel Rudolf + * @author Frank Karlitschek + * @author Georg Ehrke + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Haertl + * @author Julius Härtl + * @author Kamil Domanski + * @author Lukas Reschke + * @author Markus Goetz + * @author Morris Jobke + * @author RealRancor + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Sebastian Wessalowski + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use OC\App\DependencyAnalyzer; +use OC\App\Platform; +use OC\AppFramework\Bootstrap\Coordinator; +use OC\DB\MigrationService; +use OC\Installer; +use OC\Repair; +use OC\ServerNotAvailableException; +use OCP\App\ManagerEvent; +use OCP\AppFramework\QueryException; +use OCP\Authentication\IAlternativeLogin; +use OCP\ILogger; + +/** + * This class manages the apps. It allows them to register and integrate in the + * ownCloud ecosystem. Furthermore, this class is responsible for installing, + * upgrading and removing apps. + */ +class OC_App { + private static $adminForms = []; + private static $personalForms = []; + private static $appTypes = []; + private static $loadedApps = []; + private static $altLogin = []; + private static $alreadyRegistered = []; + public const supportedApp = 300; + public const officialApp = 200; + + /** + * clean the appId + * + * @param string $app AppId that needs to be cleaned + * @return string + */ + public static function cleanAppId(string $app): string { + return str_replace(['\0', '/', '\\', '..'], '', $app); + } + + /** + * Check if an app is loaded + * + * @param string $app + * @return bool + */ + public static function isAppLoaded(string $app): bool { + return in_array($app, self::$loadedApps, true); + } + + /** + * loads all apps + * + * @param string[] $types + * @return bool + * + * This function walks through the ownCloud directory and loads all apps + * it can find. A directory contains an app if the file /appinfo/info.xml + * exists. + * + * if $types is set to non-empty array, only apps of those types will be loaded + */ + public static function loadApps(array $types = []): bool { + if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) { + return false; + } + // Load the enabled apps here + $apps = self::getEnabledApps(); + + // Add each apps' folder as allowed class path + foreach ($apps as $app) { + $path = self::getAppPath($app); + if ($path !== false) { + self::registerAutoloading($app, $path); + } + } + + // prevent app.php from printing output + ob_start(); + foreach ($apps as $app) { + if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) { + self::loadApp($app); + } + } + ob_end_clean(); + + return true; + } + + /** + * load a single app + * + * @param string $app + * @throws Exception + */ + public static function loadApp(string $app) { + self::$loadedApps[] = $app; + $appPath = self::getAppPath($app); + if ($appPath === false) { + return; + } + + // in case someone calls loadApp() directly + self::registerAutoloading($app, $appPath); + + /** @var Coordinator $coordinator */ + $coordinator = \OC::$server->query(Coordinator::class); + $isBootable = $coordinator->isBootable($app); + + $hasAppPhpFile = is_file($appPath . '/appinfo/app.php'); + + if ($isBootable && $hasAppPhpFile) { + \OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [ + 'app' => $app, + ]); + } elseif ($hasAppPhpFile) { + \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ + 'app' => $app, + ]); + \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app); + try { + self::requireAppFile($app); + } catch (Throwable $ex) { + if ($ex instanceof ServerNotAvailableException) { + throw $ex; + } + if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) { + \OC::$server->getLogger()->logException($ex, [ + 'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(), + ]); + + // Only disable apps which are not shipped and that are not authentication apps + \OC::$server->getAppManager()->disableApp($app, true); + } else { + \OC::$server->getLogger()->logException($ex, [ + 'message' => "App $app threw an error during app.php load: " . $ex->getMessage(), + ]); + } + } + \OC::$server->getEventLogger()->end('load_app_' . $app); + } + $coordinator->bootApp($app); + + $info = self::getAppInfo($app); + if (!empty($info['activity']['filters'])) { + foreach ($info['activity']['filters'] as $filter) { + \OC::$server->getActivityManager()->registerFilter($filter); + } + } + if (!empty($info['activity']['settings'])) { + foreach ($info['activity']['settings'] as $setting) { + \OC::$server->getActivityManager()->registerSetting($setting); + } + } + if (!empty($info['activity']['providers'])) { + foreach ($info['activity']['providers'] as $provider) { + \OC::$server->getActivityManager()->registerProvider($provider); + } + } + + if (!empty($info['settings']['admin'])) { + foreach ($info['settings']['admin'] as $setting) { + \OC::$server->getSettingsManager()->registerSetting('admin', $setting); + } + } + if (!empty($info['settings']['admin-section'])) { + foreach ($info['settings']['admin-section'] as $section) { + \OC::$server->getSettingsManager()->registerSection('admin', $section); + } + } + if (!empty($info['settings']['personal'])) { + foreach ($info['settings']['personal'] as $setting) { + \OC::$server->getSettingsManager()->registerSetting('personal', $setting); + } + } + if (!empty($info['settings']['personal-section'])) { + foreach ($info['settings']['personal-section'] as $section) { + \OC::$server->getSettingsManager()->registerSection('personal', $section); + } + } + + if (!empty($info['collaboration']['plugins'])) { + // deal with one or many plugin entries + $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ? + [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin']; + foreach ($plugins as $plugin) { + if ($plugin['@attributes']['type'] === 'collaborator-search') { + $pluginInfo = [ + 'shareType' => $plugin['@attributes']['share-type'], + 'class' => $plugin['@value'], + ]; + \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo); + } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') { + \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']); + } + } + } + } + + /** + * @internal + * @param string $app + * @param string $path + * @param bool $force + */ + public static function registerAutoloading(string $app, string $path, bool $force = false) { + $key = $app . '-' . $path; + if (!$force && isset(self::$alreadyRegistered[$key])) { + return; + } + + self::$alreadyRegistered[$key] = true; + + // Register on PSR-4 composer autoloader + $appNamespace = \OC\AppFramework\App::buildAppNamespace($app); + \OC::$server->registerNamespace($app, $appNamespace); + + if (file_exists($path . '/composer/autoload.php')) { + require_once $path . '/composer/autoload.php'; + } else { + \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); + // Register on legacy autoloader + \OC::$loader->addValidRoot($path); + } + + // Register Test namespace only when testing + if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { + \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true); + } + } + + /** + * Load app.php from the given app + * + * @param string $app app name + * @throws Error + */ + private static function requireAppFile(string $app) { + // encapsulated here to avoid variable scope conflicts + require_once $app . '/appinfo/app.php'; + } + + /** + * check if an app is of a specific type + * + * @param string $app + * @param array $types + * @return bool + */ + public static function isType(string $app, array $types): bool { + $appTypes = self::getAppTypes($app); + foreach ($types as $type) { + if (array_search($type, $appTypes) !== false) { + return true; + } + } + return false; + } + + /** + * get the types of an app + * + * @param string $app + * @return array + */ + private static function getAppTypes(string $app): array { + //load the cache + if (count(self::$appTypes) == 0) { + self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types'); + } + + if (isset(self::$appTypes[$app])) { + return explode(',', self::$appTypes[$app]); + } + + return []; + } + + /** + * read app types from info.xml and cache them in the database + */ + public static function setAppTypes(string $app) { + $appManager = \OC::$server->getAppManager(); + $appData = $appManager->getAppInfo($app); + if (!is_array($appData)) { + return; + } + + if (isset($appData['types'])) { + $appTypes = implode(',', $appData['types']); + } else { + $appTypes = ''; + $appData['types'] = []; + } + + $config = \OC::$server->getConfig(); + $config->setAppValue($app, 'types', $appTypes); + + if ($appManager->hasProtectedAppType($appData['types'])) { + $enabled = $config->getAppValue($app, 'enabled', 'yes'); + if ($enabled !== 'yes' && $enabled !== 'no') { + $config->setAppValue($app, 'enabled', 'yes'); + } + } + } + + /** + * Returns apps enabled for the current user. + * + * @param bool $forceRefresh whether to refresh the cache + * @param bool $all whether to return apps for all users, not only the + * currently logged in one + * @return string[] + */ + public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array { + if (!\OC::$server->getSystemConfig()->getValue('installed', false)) { + return []; + } + // in incognito mode or when logged out, $user will be false, + // which is also the case during an upgrade + $appManager = \OC::$server->getAppManager(); + if ($all) { + $user = null; + } else { + $user = \OC::$server->getUserSession()->getUser(); + } + + if (is_null($user)) { + $apps = $appManager->getInstalledApps(); + } else { + $apps = $appManager->getEnabledAppsForUser($user); + } + $apps = array_filter($apps, function ($app) { + return $app !== 'files';//we add this manually + }); + sort($apps); + array_unshift($apps, 'files'); + return $apps; + } + + /** + * checks whether or not an app is enabled + * + * @param string $app app + * @return bool + * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId) + * + * This function checks whether or not an app is enabled. + */ + public static function isEnabled(string $app): bool { + return \OC::$server->getAppManager()->isEnabledForUser($app); + } + + /** + * enables an app + * + * @param string $appId + * @param array $groups (optional) when set, only these groups will have access to the app + * @throws \Exception + * @return void + * + * This function set an app as enabled in appconfig. + */ + public function enable(string $appId, + array $groups = []) { + + // Check if app is already downloaded + /** @var Installer $installer */ + $installer = \OC::$server->query(Installer::class); + $isDownloaded = $installer->isDownloaded($appId); + + if (!$isDownloaded) { + $installer->downloadApp($appId); + } + + $installer->installApp($appId); + + $appManager = \OC::$server->getAppManager(); + if ($groups !== []) { + $groupManager = \OC::$server->getGroupManager(); + $groupsList = []; + foreach ($groups as $group) { + $groupItem = $groupManager->get($group); + if ($groupItem instanceof \OCP\IGroup) { + $groupsList[] = $groupManager->get($group); + } + } + $appManager->enableAppForGroups($appId, $groupsList); + } else { + $appManager->enableApp($appId); + } + } + + /** + * Get the path where to install apps + * + * @return string|false + */ + public static function getInstallPath() { + if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) { + return false; + } + + foreach (OC::$APPSROOTS as $dir) { + if (isset($dir['writable']) && $dir['writable'] === true) { + return $dir['path']; + } + } + + \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR); + return null; + } + + + /** + * search for an app in all app-directories + * + * @param string $appId + * @return false|string + */ + public static function findAppInDirectories(string $appId) { + $sanitizedAppId = self::cleanAppId($appId); + if ($sanitizedAppId !== $appId) { + return false; + } + static $app_dir = []; + + if (isset($app_dir[$appId])) { + return $app_dir[$appId]; + } + + $possibleApps = []; + foreach (OC::$APPSROOTS as $dir) { + if (file_exists($dir['path'] . '/' . $appId)) { + $possibleApps[] = $dir; + } + } + + if (empty($possibleApps)) { + return false; + } elseif (count($possibleApps) === 1) { + $dir = array_shift($possibleApps); + $app_dir[$appId] = $dir; + return $dir; + } else { + $versionToLoad = []; + foreach ($possibleApps as $possibleApp) { + $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId); + if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) { + $versionToLoad = [ + 'dir' => $possibleApp, + 'version' => $version, + ]; + } + } + $app_dir[$appId] = $versionToLoad['dir']; + return $versionToLoad['dir']; + //TODO - write test + } + } + + /** + * Get the directory for the given app. + * If the app is defined in multiple directories, the first one is taken. (false if not found) + * + * @param string $appId + * @return string|false + * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath() + */ + public static function getAppPath(string $appId) { + if ($appId === null || trim($appId) === '') { + return false; + } + + if (($dir = self::findAppInDirectories($appId)) != false) { + return $dir['path'] . '/' . $appId; + } + return false; + } + + /** + * Get the path for the given app on the access + * If the app is defined in multiple directories, the first one is taken. (false if not found) + * + * @param string $appId + * @return string|false + * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath() + */ + public static function getAppWebPath(string $appId) { + if (($dir = self::findAppInDirectories($appId)) != false) { + return OC::$WEBROOT . $dir['url'] . '/' . $appId; + } + return false; + } + + /** + * get the last version of the app from appinfo/info.xml + * + * @param string $appId + * @param bool $useCache + * @return string + * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion() + */ + public static function getAppVersion(string $appId, bool $useCache = true): string { + return \OC::$server->getAppManager()->getAppVersion($appId, $useCache); + } + + /** + * get app's version based on it's path + * + * @param string $path + * @return string + */ + public static function getAppVersionByPath(string $path): string { + $infoFile = $path . '/appinfo/info.xml'; + $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true); + return isset($appData['version']) ? $appData['version'] : ''; + } + + + /** + * Read all app metadata from the info.xml file + * + * @param string $appId id of the app or the path of the info.xml file + * @param bool $path + * @param string $lang + * @return array|null + * @note all data is read from info.xml, not just pre-defined fields + * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo() + */ + public static function getAppInfo(string $appId, bool $path = false, string $lang = null) { + return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang); + } + + /** + * Returns the navigation + * + * @return array + * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll() + * + * This function returns an array containing all entries added. The + * entries are sorted by the key 'order' ascending. Additional to the keys + * given for each app the following keys exist: + * - active: boolean, signals if the user is on this navigation entry + */ + public static function getNavigation(): array { + return OC::$server->getNavigationManager()->getAll(); + } + + /** + * Returns the Settings Navigation + * + * @return string[] + * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings') + * + * This function returns an array containing all settings pages added. The + * entries are sorted by the key 'order' ascending. + */ + public static function getSettingsNavigation(): array { + return OC::$server->getNavigationManager()->getAll('settings'); + } + + /** + * get the id of loaded app + * + * @return string + */ + public static function getCurrentApp(): string { + $request = \OC::$server->getRequest(); + $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1); + $topFolder = substr($script, 0, strpos($script, '/') ?: 0); + if (empty($topFolder)) { + $path_info = $request->getPathInfo(); + if ($path_info) { + $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1); + } + } + if ($topFolder == 'apps') { + $length = strlen($topFolder); + return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: ''; + } else { + return $topFolder; + } + } + + /** + * @param string $type + * @return array + */ + public static function getForms(string $type): array { + $forms = []; + switch ($type) { + case 'admin': + $source = self::$adminForms; + break; + case 'personal': + $source = self::$personalForms; + break; + default: + return []; + } + foreach ($source as $form) { + $forms[] = include $form; + } + return $forms; + } + + /** + * register an admin form to be shown + * + * @param string $app + * @param string $page + */ + public static function registerAdmin(string $app, string $page) { + self::$adminForms[] = $app . '/' . $page . '.php'; + } + + /** + * register a personal form to be shown + * @param string $app + * @param string $page + */ + public static function registerPersonal(string $app, string $page) { + self::$personalForms[] = $app . '/' . $page . '.php'; + } + + /** + * @param array $entry + * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface + */ + public static function registerLogIn(array $entry) { + \OC::$server->getLogger()->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface'); + self::$altLogin[] = $entry; + } + + /** + * @return array + */ + public static function getAlternativeLogIns(): array { + /** @var Coordinator $bootstrapCoordinator */ + $bootstrapCoordinator = \OC::$server->query(Coordinator::class); + + foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) { + if (!in_array(IAlternativeLogin::class, class_implements($registration['class']), true)) { + \OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [ + 'option' => $registration['class'], + 'interface' => IAlternativeLogin::class, + 'app' => $registration['app'], + ]); + continue; + } + + try { + /** @var IAlternativeLogin $provider */ + $provider = \OC::$server->query($registration['class']); + } catch (QueryException $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Alternative login option {option} can not be initialised.', + 'option' => $registration['class'], + 'app' => $registration['app'], + ]); + } + + try { + $provider->load(); + + self::$altLogin[] = [ + 'name' => $provider->getLabel(), + 'href' => $provider->getLink(), + 'style' => $provider->getClass(), + ]; + } catch (Throwable $e) { + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Alternative login option {option} had an error while loading.', + 'option' => $registration['class'], + 'app' => $registration['app'], + ]); + } + } + + return self::$altLogin; + } + + /** + * get a list of all apps in the apps folder + * + * @return string[] an array of app names (string IDs) + * @todo: change the name of this method to getInstalledApps, which is more accurate + */ + public static function getAllApps(): array { + $apps = []; + + foreach (OC::$APPSROOTS as $apps_dir) { + if (!is_readable($apps_dir['path'])) { + \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN); + continue; + } + $dh = opendir($apps_dir['path']); + + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) { + $apps[] = $file; + } + } + } + } + + $apps = array_unique($apps); + + return $apps; + } + + /** + * List all apps, this is used in apps.php + * + * @return array + */ + public function listAllApps(): array { + $installedApps = OC_App::getAllApps(); + + $appManager = \OC::$server->getAppManager(); + //we don't want to show configuration for these + $blacklist = $appManager->getAlwaysEnabledApps(); + $appList = []; + $langCode = \OC::$server->getL10N('core')->getLanguageCode(); + $urlGenerator = \OC::$server->getURLGenerator(); + /** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */ + $subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class); + $supportedApps = $subscriptionRegistry->delegateGetSupportedApps(); + + foreach ($installedApps as $app) { + if (array_search($app, $blacklist) === false) { + $info = OC_App::getAppInfo($app, false, $langCode); + if (!is_array($info)) { + \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR); + continue; + } + + if (!isset($info['name'])) { + \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR); + continue; + } + + $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no'); + $info['groups'] = null; + if ($enabled === 'yes') { + $active = true; + } elseif ($enabled === 'no') { + $active = false; + } else { + $active = true; + $info['groups'] = $enabled; + } + + $info['active'] = $active; + + if ($appManager->isShipped($app)) { + $info['internal'] = true; + $info['level'] = self::officialApp; + $info['removable'] = false; + } else { + $info['internal'] = false; + $info['removable'] = true; + } + + if (in_array($app, $supportedApps)) { + $info['level'] = self::supportedApp; + } + + $appPath = self::getAppPath($app); + if ($appPath !== false) { + $appIcon = $appPath . '/img/' . $app . '.svg'; + if (file_exists($appIcon)) { + $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg'); + $info['previewAsIcon'] = true; + } else { + $appIcon = $appPath . '/img/app.svg'; + if (file_exists($appIcon)) { + $info['preview'] = $urlGenerator->imagePath($app, 'app.svg'); + $info['previewAsIcon'] = true; + } + } + } + // fix documentation + if (isset($info['documentation']) && is_array($info['documentation'])) { + foreach ($info['documentation'] as $key => $url) { + // If it is not an absolute URL we assume it is a key + // i.e. admin-ldap will get converted to go.php?to=admin-ldap + if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) { + $url = $urlGenerator->linkToDocs($url); + } + + $info['documentation'][$key] = $url; + } + } + + $info['version'] = OC_App::getAppVersion($app); + $appList[] = $info; + } + } + + return $appList; + } + + public static function shouldUpgrade(string $app): bool { + $versions = self::getAppVersions(); + $currentVersion = OC_App::getAppVersion($app); + if ($currentVersion && isset($versions[$app])) { + $installedVersion = $versions[$app]; + if (!version_compare($currentVersion, $installedVersion, '=')) { + return true; + } + } + return false; + } + + /** + * Adjust the number of version parts of $version1 to match + * the number of version parts of $version2. + * + * @param string $version1 version to adjust + * @param string $version2 version to take the number of parts from + * @return string shortened $version1 + */ + private static function adjustVersionParts(string $version1, string $version2): string { + $version1 = explode('.', $version1); + $version2 = explode('.', $version2); + // reduce $version1 to match the number of parts in $version2 + while (count($version1) > count($version2)) { + array_pop($version1); + } + // if $version1 does not have enough parts, add some + while (count($version1) < count($version2)) { + $version1[] = '0'; + } + return implode('.', $version1); + } + + /** + * Check whether the current ownCloud version matches the given + * application's version requirements. + * + * The comparison is made based on the number of parts that the + * app info version has. For example for ownCloud 6.0.3 if the + * app info version is expecting version 6.0, the comparison is + * made on the first two parts of the ownCloud version. + * This means that it's possible to specify "requiremin" => 6 + * and "requiremax" => 6 and it will still match ownCloud 6.0.3. + * + * @param string $ocVersion ownCloud version to check against + * @param array $appInfo app info (from xml) + * + * @return boolean true if compatible, otherwise false + */ + public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool { + $requireMin = ''; + $requireMax = ''; + if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) { + $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version']; + } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) { + $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version']; + } elseif (isset($appInfo['requiremin'])) { + $requireMin = $appInfo['requiremin']; + } elseif (isset($appInfo['require'])) { + $requireMin = $appInfo['require']; + } + + if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) { + $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version']; + } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) { + $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version']; + } elseif (isset($appInfo['requiremax'])) { + $requireMax = $appInfo['requiremax']; + } + + if (!empty($requireMin) + && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<') + ) { + return false; + } + + if (!$ignoreMax && !empty($requireMax) + && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>') + ) { + return false; + } + + return true; + } + + /** + * get the installed version of all apps + */ + public static function getAppVersions() { + static $versions; + + if (!$versions) { + $appConfig = \OC::$server->getAppConfig(); + $versions = $appConfig->getValues(false, 'installed_version'); + } + return $versions; + } + + /** + * update the database for the app and call the update script + * + * @param string $appId + * @return bool + */ + public static function updateApp(string $appId): bool { + $appPath = self::getAppPath($appId); + if ($appPath === false) { + return false; + } + + \OC::$server->getAppManager()->clearAppsCache(); + $appData = self::getAppInfo($appId); + + self::registerAutoloading($appId, $appPath, true); + self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']); + + if (file_exists($appPath . '/appinfo/database.xml')) { + OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml'); + } else { + $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection()); + $ms->migrate(); + } + + self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']); + self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']); + // update appversion in app manager + \OC::$server->getAppManager()->clearAppsCache(); + \OC::$server->getAppManager()->getAppVersion($appId, false); + + // run upgrade code + if (file_exists($appPath . '/appinfo/update.php')) { + self::loadApp($appId); + include $appPath . '/appinfo/update.php'; + } + self::setupBackgroundJobs($appData['background-jobs']); + + //set remote/public handlers + if (array_key_exists('ocsid', $appData)) { + \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']); + } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) { + \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid'); + } + foreach ($appData['remote'] as $name => $path) { + \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path); + } + foreach ($appData['public'] as $name => $path) { + \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path); + } + + self::setAppTypes($appId); + + $version = \OC_App::getAppVersion($appId); + \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version); + + \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent( + ManagerEvent::EVENT_APP_UPDATE, $appId + )); + + return true; + } + + /** + * @param string $appId + * @param string[] $steps + * @throws \OC\NeedsUpdateException + */ + public static function executeRepairSteps(string $appId, array $steps) { + if (empty($steps)) { + return; + } + // load the app + self::loadApp($appId); + + $dispatcher = OC::$server->getEventDispatcher(); + + // load the steps + $r = new Repair([], $dispatcher); + foreach ($steps as $step) { + try { + $r->addStep($step); + } catch (Exception $ex) { + $r->emit('\OC\Repair', 'error', [$ex->getMessage()]); + \OC::$server->getLogger()->logException($ex); + } + } + // run the steps + $r->run(); + } + + public static function setupBackgroundJobs(array $jobs) { + $queue = \OC::$server->getJobList(); + foreach ($jobs as $job) { + $queue->add($job); + } + } + + /** + * @param string $appId + * @param string[] $steps + */ + private static function setupLiveMigrations(string $appId, array $steps) { + $queue = \OC::$server->getJobList(); + foreach ($steps as $step) { + $queue->add('OC\Migration\BackgroundRepair', [ + 'app' => $appId, + 'step' => $step]); + } + } + + /** + * @param string $appId + * @return \OC\Files\View|false + */ + public static function getStorage(string $appId) { + if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check + if (\OC::$server->getUserSession()->isLoggedIn()) { + $view = new \OC\Files\View('/' . OC_User::getUser()); + if (!$view->file_exists($appId)) { + $view->mkdir($appId); + } + return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId); + } else { + \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR); + return false; + } + } else { + \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR); + return false; + } + } + + protected static function findBestL10NOption(array $options, string $lang): string { + // only a single option + if (isset($options['@value'])) { + return $options['@value']; + } + + $fallback = $similarLangFallback = $englishFallback = false; + + $lang = strtolower($lang); + $similarLang = $lang; + if (strpos($similarLang, '_')) { + // For "de_DE" we want to find "de" and the other way around + $similarLang = substr($lang, 0, strpos($lang, '_')); + } + + foreach ($options as $option) { + if (is_array($option)) { + if ($fallback === false) { + $fallback = $option['@value']; + } + + if (!isset($option['@attributes']['lang'])) { + continue; + } + + $attributeLang = strtolower($option['@attributes']['lang']); + if ($attributeLang === $lang) { + return $option['@value']; + } + + if ($attributeLang === $similarLang) { + $similarLangFallback = $option['@value']; + } elseif (strpos($attributeLang, $similarLang . '_') === 0) { + if ($similarLangFallback === false) { + $similarLangFallback = $option['@value']; + } + } + } else { + $englishFallback = $option; + } + } + + if ($similarLangFallback !== false) { + return $similarLangFallback; + } elseif ($englishFallback !== false) { + return $englishFallback; + } + return (string) $fallback; + } + + /** + * parses the app data array and enhanced the 'description' value + * + * @param array $data the app data + * @param string $lang + * @return array improved app data + */ + public static function parseAppInfo(array $data, $lang = null): array { + if ($lang && isset($data['name']) && is_array($data['name'])) { + $data['name'] = self::findBestL10NOption($data['name'], $lang); + } + if ($lang && isset($data['summary']) && is_array($data['summary'])) { + $data['summary'] = self::findBestL10NOption($data['summary'], $lang); + } + if ($lang && isset($data['description']) && is_array($data['description'])) { + $data['description'] = trim(self::findBestL10NOption($data['description'], $lang)); + } elseif (isset($data['description']) && is_string($data['description'])) { + $data['description'] = trim($data['description']); + } else { + $data['description'] = ''; + } + + return $data; + } + + /** + * @param \OCP\IConfig $config + * @param \OCP\IL10N $l + * @param array $info + * @throws \Exception + */ + public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) { + $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); + $missing = $dependencyAnalyzer->analyze($info, $ignoreMax); + if (!empty($missing)) { + $missingMsg = implode(PHP_EOL, $missing); + throw new \Exception( + $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s', + [$info['name'], $missingMsg] + ) + ); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB.php new file mode 100644 index 0000000..1d2e9bd --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB.php @@ -0,0 +1,246 @@ + + * @author Arthur Schiwon + * @author Bart Visscher + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OCP\ILogger; + +/** + * This class manages the access to the database. It basically is a wrapper for + * Doctrine with some adaptions. + */ +class OC_DB { + + /** + * get MDB2 schema manager + * + * @return \OC\DB\MDB2SchemaManager + */ + private static function getMDB2SchemaManager() { + return new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection()); + } + + /** + * Prepare a SQL query + * @param string $query Query string + * @param int|null $limit + * @param int|null $offset + * @param bool|null $isManipulation + * @throws \OC\DatabaseException + * @return OC_DB_StatementWrapper prepared SQL query + * + * SQL query via Doctrine prepare(), needs to be execute()'d! + */ + public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) { + $connection = \OC::$server->getDatabaseConnection(); + + if ($isManipulation === null) { + //try to guess, so we return the number of rows on manipulations + $isManipulation = self::isManipulation($query); + } + + // return the result + try { + $result =$connection->prepare($query, $limit, $offset); + } catch (\Doctrine\DBAL\DBALException $e) { + throw new \OC\DatabaseException($e->getMessage()); + } + // differentiate between query and manipulation + $result = new OC_DB_StatementWrapper($result, $isManipulation); + return $result; + } + + /** + * tries to guess the type of statement based on the first 10 characters + * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements + * + * @param string $sql + * @return bool + */ + public static function isManipulation($sql) { + $selectOccurrence = stripos($sql, 'SELECT'); + if ($selectOccurrence !== false && $selectOccurrence < 10) { + return false; + } + $insertOccurrence = stripos($sql, 'INSERT'); + if ($insertOccurrence !== false && $insertOccurrence < 10) { + return true; + } + $updateOccurrence = stripos($sql, 'UPDATE'); + if ($updateOccurrence !== false && $updateOccurrence < 10) { + return true; + } + $deleteOccurrence = stripos($sql, 'DELETE'); + if ($deleteOccurrence !== false && $deleteOccurrence < 10) { + return true; + } + return false; + } + + /** + * execute a prepared statement, on error write log and throw exception + * @param mixed $stmt OC_DB_StatementWrapper, + * an array with 'sql' and optionally 'limit' and 'offset' keys + * .. or a simple sql query string + * @param array $parameters + * @return OC_DB_StatementWrapper + * @throws \OC\DatabaseException + */ + public static function executeAudited($stmt, array $parameters = []) { + if (is_string($stmt)) { + // convert to an array with 'sql' + if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT + // TODO try to convert LIMIT OFFSET notation to parameters + $message = 'LIMIT and OFFSET are forbidden for portability reasons,' + . ' pass an array with \'limit\' and \'offset\' instead'; + throw new \OC\DatabaseException($message); + } + $stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null]; + } + if (is_array($stmt)) { + // convert to prepared statement + if (! array_key_exists('sql', $stmt)) { + $message = 'statement array must at least contain key \'sql\''; + throw new \OC\DatabaseException($message); + } + if (! array_key_exists('limit', $stmt)) { + $stmt['limit'] = null; + } + if (! array_key_exists('limit', $stmt)) { + $stmt['offset'] = null; + } + $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']); + } + self::raiseExceptionOnError($stmt, 'Could not prepare statement'); + if ($stmt instanceof OC_DB_StatementWrapper) { + $result = $stmt->execute($parameters); + self::raiseExceptionOnError($result, 'Could not execute statement'); + } else { + if (is_object($stmt)) { + $message = 'Expected a prepared statement or array got ' . get_class($stmt); + } else { + $message = 'Expected a prepared statement or array got ' . gettype($stmt); + } + throw new \OC\DatabaseException($message); + } + return $result; + } + + /** + * saves database schema to xml file + * @param string $file name of file + * @return bool + * + * TODO: write more documentation + */ + public static function getDbStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + return $schemaManager->getDbStructure($file); + } + + /** + * Creates tables from XML file + * @param string $file file to read structure from + * @return bool + * + * TODO: write more documentation + */ + public static function createDbFromStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + return $schemaManager->createDbFromStructure($file); + } + + /** + * update the database schema + * @param string $file file to read structure from + * @throws Exception + * @return string|boolean + * @suppress PhanDeprecatedFunction + */ + public static function updateDbFromStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + try { + $result = $schemaManager->updateDbFromStructure($file); + } catch (Exception $e) { + \OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', ILogger::FATAL); + throw $e; + } + return $result; + } + + /** + * remove all tables defined in a database structure xml file + * @param string $file the xml file describing the tables + */ + public static function removeDBStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + $schemaManager->removeDBStructure($file); + } + + /** + * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException + * @param mixed $result + * @param string $message + * @return void + * @throws \OC\DatabaseException + */ + public static function raiseExceptionOnError($result, $message = null) { + if ($result === false) { + if ($message === null) { + $message = self::getErrorMessage(); + } else { + $message .= ', Root cause:' . self::getErrorMessage(); + } + throw new \OC\DatabaseException($message); + } + } + + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @return string + */ + public static function getErrorMessage() { + $connection = \OC::$server->getDatabaseConnection(); + return $connection->getError(); + } + + /** + * Checks if a table exists in the database - the database prefix will be prepended + * + * @param string $table + * @return bool + * @throws \OC\DatabaseException + */ + public static function tableExists($table) { + $connection = \OC::$server->getDatabaseConnection(); + return $connection->tableExists($table); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB_StatementWrapper.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB_StatementWrapper.php new file mode 100644 index 0000000..d4072ca --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_DB_StatementWrapper.php @@ -0,0 +1,120 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Piotr Mrówczyński + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * small wrapper around \Doctrine\DBAL\Driver\Statement to make it behave, more like an MDB2 Statement + * + * @method boolean bindValue(mixed $param, mixed $value, integer $type = null); + * @method string errorCode(); + * @method array errorInfo(); + * @method integer rowCount(); + * @method array fetchAll(integer $fetchMode = null); + */ +class OC_DB_StatementWrapper { + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $statement = null; + private $isManipulation = false; + private $lastArguments = []; + + /** + * @param boolean $isManipulation + */ + public function __construct($statement, $isManipulation) { + $this->statement = $statement; + $this->isManipulation = $isManipulation; + } + + /** + * pass all other function directly to the \Doctrine\DBAL\Driver\Statement + */ + public function __call($name,$arguments) { + return call_user_func_array([$this->statement,$name], $arguments); + } + + /** + * make execute return the result instead of a bool + * + * @param array $input + * @return \OC_DB_StatementWrapper|int|bool + */ + public function execute($input= []) { + $this->lastArguments = $input; + if (count($input) > 0) { + $result = $this->statement->execute($input); + } else { + $result = $this->statement->execute(); + } + + if ($result === false) { + return false; + } + if ($this->isManipulation) { + return $this->statement->rowCount(); + } else { + return $this; + } + } + + /** + * provide an alias for fetch + * + * @return mixed + */ + public function fetchRow() { + return $this->statement->fetch(); + } + + /** + * Provide a simple fetchOne. + * + * fetch single column from the next row + * @param int $column the column number to fetch + * @return string + */ + public function fetchOne($column = 0) { + return $this->statement->fetchColumn($column); + } + + /** + * Binds a PHP variable to a corresponding named or question mark placeholder in the + * SQL statement that was use to prepare the statement. + * + * @param mixed $column Either the placeholder name or the 1-indexed placeholder index + * @param mixed $variable The variable to bind + * @param integer|null $type one of the PDO::PARAM_* constants + * @param integer|null $length max length when using an OUT bind + * @return boolean + */ + public function bindParam($column, &$variable, $type = null, $length = null) { + return $this->statement->bindParam($column, $variable, $type, $length); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Defaults.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Defaults.php new file mode 100644 index 0000000..a9f588e --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Defaults.php @@ -0,0 +1,333 @@ + + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Jan-Christoph Borchardt + * @author Jörn Friedrich Dreyer + * @author Julius Haertl + * @author Julius Härtl + * @author Lukas Reschke + * @author Markus Staab + * @author Michael Weimann + * @author Morris Jobke + * @author Pascal de Bruijn + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author scolebrook + * @author Thomas Müller + * @author Volkan Gezer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +class OC_Defaults { + private $theme; + + private $defaultEntity; + private $defaultName; + private $defaultTitle; + private $defaultBaseUrl; + private $defaultSyncClientUrl; + private $defaultiOSClientUrl; + private $defaultiTunesAppId; + private $defaultAndroidClientUrl; + private $defaultDocBaseUrl; + private $defaultDocVersion; + private $defaultSlogan; + private $defaultColorPrimary; + private $defaultTextColorPrimary; + + public function __construct() { + $config = \OC::$server->getConfig(); + + $this->defaultEntity = 'Nextcloud'; /* e.g. company name, used for footers and copyright notices */ + $this->defaultName = 'Nextcloud'; /* short name, used when referring to the software */ + $this->defaultTitle = 'Nextcloud'; /* can be a longer name, for titles */ + $this->defaultBaseUrl = 'https://nextcloud.com'; + $this->defaultSyncClientUrl = $config->getSystemValue('customclient_desktop', 'https://nextcloud.com/install/#install-clients'); + $this->defaultiOSClientUrl = $config->getSystemValue('customclient_ios', 'https://geo.itunes.apple.com/us/app/nextcloud/id1125420102?mt=8'); + $this->defaultiTunesAppId = $config->getSystemValue('customclient_ios_appid', '1125420102'); + $this->defaultAndroidClientUrl = $config->getSystemValue('customclient_android', 'https://play.google.com/store/apps/details?id=com.nextcloud.client'); + $this->defaultDocBaseUrl = 'https://docs.nextcloud.com'; + $this->defaultDocVersion = \OC_Util::getVersion()[0]; // used to generate doc links + $this->defaultColorPrimary = '#0082c9'; + $this->defaultTextColorPrimary = '#ffffff'; + + $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php'; + if (file_exists($themePath)) { + // prevent defaults.php from printing output + ob_start(); + require_once $themePath; + ob_end_clean(); + if (class_exists('OC_Theme')) { + $this->theme = new OC_Theme(); + } + } + } + + /** + * @param string $method + */ + private function themeExist($method) { + if (isset($this->theme) && method_exists($this->theme, $method)) { + return true; + } + return false; + } + + /** + * Returns the base URL + * @return string URL + */ + public function getBaseUrl() { + if ($this->themeExist('getBaseUrl')) { + return $this->theme->getBaseUrl(); + } else { + return $this->defaultBaseUrl; + } + } + + /** + * Returns the URL where the sync clients are listed + * @return string URL + */ + public function getSyncClientUrl() { + if ($this->themeExist('getSyncClientUrl')) { + return $this->theme->getSyncClientUrl(); + } else { + return $this->defaultSyncClientUrl; + } + } + + /** + * Returns the URL to the App Store for the iOS Client + * @return string URL + */ + public function getiOSClientUrl() { + if ($this->themeExist('getiOSClientUrl')) { + return $this->theme->getiOSClientUrl(); + } else { + return $this->defaultiOSClientUrl; + } + } + + /** + * Returns the AppId for the App Store for the iOS Client + * @return string AppId + */ + public function getiTunesAppId() { + if ($this->themeExist('getiTunesAppId')) { + return $this->theme->getiTunesAppId(); + } else { + return $this->defaultiTunesAppId; + } + } + + /** + * Returns the URL to Google Play for the Android Client + * @return string URL + */ + public function getAndroidClientUrl() { + if ($this->themeExist('getAndroidClientUrl')) { + return $this->theme->getAndroidClientUrl(); + } else { + return $this->defaultAndroidClientUrl; + } + } + + /** + * Returns the documentation URL + * @return string URL + */ + public function getDocBaseUrl() { + if ($this->themeExist('getDocBaseUrl')) { + return $this->theme->getDocBaseUrl(); + } else { + return $this->defaultDocBaseUrl; + } + } + + /** + * Returns the title + * @return string title + */ + public function getTitle() { + if ($this->themeExist('getTitle')) { + return $this->theme->getTitle(); + } else { + return $this->defaultTitle; + } + } + + /** + * Returns the short name of the software + * @return string title + */ + public function getName() { + if ($this->themeExist('getName')) { + return $this->theme->getName(); + } else { + return $this->defaultName; + } + } + + /** + * Returns the short name of the software containing HTML strings + * @return string title + */ + public function getHTMLName() { + if ($this->themeExist('getHTMLName')) { + return $this->theme->getHTMLName(); + } else { + return $this->defaultName; + } + } + + /** + * Returns entity (e.g. company name) - used for footer, copyright + * @return string entity name + */ + public function getEntity() { + if ($this->themeExist('getEntity')) { + return $this->theme->getEntity(); + } else { + return $this->defaultEntity; + } + } + + /** + * Returns slogan + * @return string slogan + */ + public function getSlogan(?string $lang = null) { + if ($this->themeExist('getSlogan')) { + return $this->theme->getSlogan($lang); + } else { + if ($this->defaultSlogan === null) { + $l10n = \OC::$server->getL10N('lib', $lang); + $this->defaultSlogan = $l10n->t('a safe home for all your data'); + } + return $this->defaultSlogan; + } + } + + /** + * Returns logo claim + * @return string logo claim + * @deprecated 13.0.0 + */ + public function getLogoClaim() { + return ''; + } + + /** + * Returns short version of the footer + * @return string short footer + */ + public function getShortFooter() { + if ($this->themeExist('getShortFooter')) { + $footer = $this->theme->getShortFooter(); + } else { + $footer = '' .$this->getEntity() . ''. + ' – ' . $this->getSlogan(); + } + + return $footer; + } + + /** + * Returns long version of the footer + * @return string long footer + */ + public function getLongFooter() { + if ($this->themeExist('getLongFooter')) { + $footer = $this->theme->getLongFooter(); + } else { + $footer = $this->getShortFooter(); + } + + return $footer; + } + + /** + * @param string $key + * @return string URL to doc with key + */ + public function buildDocLinkToKey($key) { + if ($this->themeExist('buildDocLinkToKey')) { + return $this->theme->buildDocLinkToKey($key); + } + return $this->getDocBaseUrl() . '/server/' . $this->defaultDocVersion . '/go.php?to=' . $key; + } + + /** + * Returns primary color + * @return string + */ + public function getColorPrimary() { + if ($this->themeExist('getColorPrimary')) { + return $this->theme->getColorPrimary(); + } + if ($this->themeExist('getMailHeaderColor')) { + return $this->theme->getMailHeaderColor(); + } + return $this->defaultColorPrimary; + } + + /** + * @return array scss variables to overwrite + */ + public function getScssVariables() { + if ($this->themeExist('getScssVariables')) { + return $this->theme->getScssVariables(); + } + return []; + } + + public function shouldReplaceIcons() { + return false; + } + + /** + * Themed logo url + * + * @param bool $useSvg Whether to point to the SVG image or a fallback + * @return string + */ + public function getLogo($useSvg = true) { + if ($this->themeExist('getLogo')) { + return $this->theme->getLogo($useSvg); + } + + if ($useSvg) { + $logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.svg'); + } else { + $logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.png'); + } + return $logo . '?v=' . hash('sha1', implode('.', \OCP\Util::getVersion())); + } + + public function getTextColorPrimary() { + if ($this->themeExist('getTextColorPrimary')) { + return $this->theme->getTextColorPrimary(); + } + return $this->defaultTextColorPrimary; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_EventSource.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_EventSource.php new file mode 100644 index 0000000..2d29f31 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_EventSource.php @@ -0,0 +1,132 @@ + + * @author Christian Oliff + * @author Christoph Wurst + * @author Felix Moeller + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * wrapper for server side events (https://en.wikipedia.org/wiki/Server-sent_events) + * includes a fallback for older browsers and IE + * + * use server side events with caution, to many open requests can hang the server + */ +class OC_EventSource implements \OCP\IEventSource { + /** + * @var bool + */ + private $fallback; + + /** + * @var int + */ + private $fallBackId = 0; + + /** + * @var bool + */ + private $started = false; + + protected function init() { + if ($this->started) { + return; + } + $this->started = true; + + // prevent php output buffering, caching and nginx buffering + OC_Util::obEnd(); + header('Cache-Control: no-cache'); + header('X-Accel-Buffering: no'); + $this->fallback = isset($_GET['fallback']) and $_GET['fallback'] == 'true'; + if ($this->fallback) { + $this->fallBackId = (int)$_GET['fallback_id']; + /** + * FIXME: The default content-security-policy of ownCloud forbids inline + * JavaScript for security reasons. IE starting on Windows 10 will + * however also obey the CSP which will break the event source fallback. + * + * As a workaround thus we set a custom policy which allows the execution + * of inline JavaScript. + * + * @link https://github.com/owncloud/core/issues/14286 + */ + header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'"); + header("Content-Type: text/html"); + echo str_repeat('' . PHP_EOL, 10); //dummy data to keep IE happy + } else { + header("Content-Type: text/event-stream"); + } + if (!\OC::$server->getRequest()->passesStrictCookieCheck()) { + header('Location: '.\OC::$WEBROOT); + exit(); + } + if (!\OC::$server->getRequest()->passesCSRFCheck()) { + $this->send('error', 'Possible CSRF attack. Connection will be closed.'); + $this->close(); + exit(); + } + flush(); + } + + /** + * send a message to the client + * + * @param string $type + * @param mixed $data + * + * @throws \BadMethodCallException + * if only one parameter is given, a typeless message will be send with that parameter as data + * @suppress PhanDeprecatedFunction + */ + public function send($type, $data = null) { + if ($data and !preg_match('/^[A-Za-z0-9_]+$/', $type)) { + throw new BadMethodCallException('Type needs to be alphanumeric ('. $type .')'); + } + $this->init(); + if (is_null($data)) { + $data = $type; + $type = null; + } + if ($this->fallback) { + $response = '' . PHP_EOL; + echo $response; + } else { + if ($type) { + echo 'event: ' . $type . PHP_EOL; + } + echo 'data: ' . OC_JSON::encode($data) . PHP_EOL; + } + echo PHP_EOL; + flush(); + } + + /** + * close the connection of the event source + */ + public function close() { + $this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_FileChunking.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_FileChunking.php new file mode 100644 index 0000000..8aef295 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_FileChunking.php @@ -0,0 +1,186 @@ + + * @author Christoph Wurst + * @author Felix Moeller + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +class OC_FileChunking { + protected $info; + protected $cache; + + /** + * TTL of chunks + * + * @var int + */ + protected $ttl; + + public static function decodeName($name) { + preg_match('/(?P.*)-chunking-(?P\d+)-(?P\d+)-(?P\d+)/', $name, $matches); + return $matches; + } + + /** + * @param string[] $info + */ + public function __construct($info) { + $this->info = $info; + $this->ttl = \OC::$server->getConfig()->getSystemValue('cache_chunk_gc_ttl', 86400); + } + + public function getPrefix() { + $name = $this->info['name']; + $transferid = $this->info['transferid']; + + return $name.'-chunking-'.$transferid.'-'; + } + + protected function getCache() { + if (!isset($this->cache)) { + $this->cache = new \OC\Cache\File(); + } + return $this->cache; + } + + /** + * Stores the given $data under the given $key - the number of stored bytes is returned + * + * @param string $index + * @param resource $data + * @return int + */ + public function store($index, $data) { + $cache = $this->getCache(); + $name = $this->getPrefix().$index; + $cache->set($name, $data, $this->ttl); + + return $cache->size($name); + } + + public function isComplete() { + $prefix = $this->getPrefix(); + $cache = $this->getCache(); + $chunkcount = (int)$this->info['chunkcount']; + + for ($i=($chunkcount-1); $i >= 0; $i--) { + if (!$cache->hasKey($prefix.$i)) { + return false; + } + } + + return true; + } + + /** + * Assembles the chunks into the file specified by the path. + * Chunks are deleted afterwards. + * + * @param resource $f target path + * + * @return integer assembled file size + * + * @throws \OC\InsufficientStorageException when file could not be fully + * assembled due to lack of free space + */ + public function assemble($f) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $count = 0; + for ($i = 0; $i < $this->info['chunkcount']; $i++) { + $chunk = $cache->get($prefix.$i); + // remove after reading to directly save space + $cache->remove($prefix.$i); + $count += fwrite($f, $chunk); + // let php release the memory to work around memory exhausted error with php 5.6 + $chunk = null; + } + + return $count; + } + + /** + * Returns the size of the chunks already present + * @return integer size in bytes + */ + public function getCurrentSize() { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $total = 0; + for ($i = 0; $i < $this->info['chunkcount']; $i++) { + $total += $cache->size($prefix.$i); + } + return $total; + } + + /** + * Removes all chunks which belong to this transmission + */ + public function cleanup() { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + for ($i=0; $i < $this->info['chunkcount']; $i++) { + $cache->remove($prefix.$i); + } + } + + /** + * Removes one specific chunk + * @param string $index + */ + public function remove($index) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $cache->remove($prefix.$index); + } + + /** + * Assembles the chunks into the file specified by the path. + * Also triggers the relevant hooks and proxies. + * + * @param \OC\Files\Storage\Storage $storage storage + * @param string $path target path relative to the storage + * @return bool true on success or false if file could not be created + * + * @throws \OC\ServerNotAvailableException + */ + public function file_assemble($storage, $path) { + // use file_put_contents as method because that best matches what this function does + if (\OC\Files\Filesystem::isValidPath($path)) { + $target = $storage->fopen($path, 'w'); + if ($target) { + $count = $this->assemble($target); + fclose($target); + return $count > 0; + } else { + return false; + } + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Files.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Files.php new file mode 100644 index 0000000..f5f91fc --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Files.php @@ -0,0 +1,426 @@ + + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Frank Karlitschek + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Ko- + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Nicolai Ehemann + * @author Piotr Filiciak + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thibaut GRIDEL + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\Files\View; +use OC\Streamer; +use OCP\Lock\ILockingProvider; + +/** + * Class for file server access + * + */ +class OC_Files { + public const FILE = 1; + public const ZIP_FILES = 2; + public const ZIP_DIR = 3; + + public const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB + + + private static $multipartBoundary = ''; + + /** + * @return string + */ + private static function getBoundary() { + if (empty(self::$multipartBoundary)) { + self::$multipartBoundary = md5(mt_rand()); + } + return self::$multipartBoundary; + } + + /** + * @param string $filename + * @param string $name + * @param array $rangeArray ('from'=>int,'to'=>int), ... + */ + private static function sendHeaders($filename, $name, array $rangeArray) { + OC_Response::setContentDispositionHeader($name, 'attachment'); + header('Content-Transfer-Encoding: binary', true); + header('Pragma: public');// enable caching in IE + header('Expires: 0'); + header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); + $fileSize = \OC\Files\Filesystem::filesize($filename); + $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); + if ($fileSize > -1) { + if (!empty($rangeArray)) { + http_response_code(206); + header('Accept-Ranges: bytes', true); + if (count($rangeArray) > 1) { + $type = 'multipart/byteranges; boundary='.self::getBoundary(); + // no Content-Length header here + } else { + header(sprintf('Content-Range: bytes %d-%d/%d', $rangeArray[0]['from'], $rangeArray[0]['to'], $fileSize), true); + OC_Response::setContentLengthHeader($rangeArray[0]['to'] - $rangeArray[0]['from'] + 1); + } + } else { + OC_Response::setContentLengthHeader($fileSize); + } + } + header('Content-Type: '.$type, true); + } + + /** + * return the content of a file or return a zip file containing multiple files + * + * @param string $dir + * @param string $files ; separated list of files to download + * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header + */ + public static function get($dir, $files, $params = null) { + $view = \OC\Files\Filesystem::getView(); + $getType = self::FILE; + $filename = $dir; + try { + if (is_array($files) && count($files) === 1) { + $files = $files[0]; + } + + if (!is_array($files)) { + $filename = $dir . '/' . $files; + if (!$view->is_dir($filename)) { + self::getSingleFile($view, $dir, $files, is_null($params) ? [] : $params); + return; + } + } + + $name = 'download'; + if (is_array($files)) { + $getType = self::ZIP_FILES; + $basename = basename($dir); + if ($basename) { + $name = $basename; + } + + $filename = $dir . '/' . $name; + } else { + $filename = $dir . '/' . $files; + $getType = self::ZIP_DIR; + // downloading root ? + if ($files !== '') { + $name = $files; + } + } + + self::lockFiles($view, $dir, $files); + + /* Calculate filesize and number of files */ + if ($getType === self::ZIP_FILES) { + $fileInfos = []; + $fileSize = 0; + foreach ($files as $file) { + $fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $file); + $fileSize += $fileInfo->getSize(); + $fileInfos[] = $fileInfo; + } + $numberOfFiles = self::getNumberOfFiles($fileInfos); + } elseif ($getType === self::ZIP_DIR) { + $fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $files); + $fileSize = $fileInfo->getSize(); + $numberOfFiles = self::getNumberOfFiles([$fileInfo]); + } + + $streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles); + OC_Util::obEnd(); + + $streamer->sendHeaders($name); + $executionTime = (int)OC::$server->get(IniGetWrapper::class)->getNumeric('max_execution_time'); + if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { + @set_time_limit(0); + } + ignore_user_abort(true); + + if ($getType === self::ZIP_FILES) { + foreach ($files as $file) { + $file = $dir . '/' . $file; + if (\OC\Files\Filesystem::is_file($file)) { + $userFolder = \OC::$server->getRootFolder()->get(\OC\Files\Filesystem::getRoot()); + $file = $userFolder->get($file); + if ($file instanceof \OC\Files\Node\File) { + try { + $fh = $file->fopen('r'); + } catch (\OCP\Files\NotPermittedException $e) { + continue; + } + $fileSize = $file->getSize(); + $fileTime = $file->getMTime(); + } else { + // File is not a file? … + \OC::$server->getLogger()->debug( + 'File given, but no Node available. Name {file}', + [ 'app' => 'files', 'file' => $file ] + ); + continue; + } + $streamer->addFileFromStream($fh, $file->getName(), $fileSize, $fileTime); + fclose($fh); + } elseif (\OC\Files\Filesystem::is_dir($file)) { + $streamer->addDirRecursive($file); + } + } + } elseif ($getType === self::ZIP_DIR) { + $file = $dir . '/' . $files; + $streamer->addDirRecursive($file); + } + $streamer->finalize(); + set_time_limit($executionTime); + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + } catch (\OCP\Lock\LockedException $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; + \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint, 200); + } catch (\OCP\Files\ForbiddenException $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage(), 200); + } catch (\Exception $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; + \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint, 200); + } + } + + /** + * @param string $rangeHeaderPos + * @param int $fileSize + * @return array $rangeArray ('from'=>int,'to'=>int), ... + */ + private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize) { + $rArray=explode(',', $rangeHeaderPos); + $minOffset = 0; + $ind = 0; + + $rangeArray = []; + + foreach ($rArray as $value) { + $ranges = explode('-', $value); + if (is_numeric($ranges[0])) { + if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999 + $ranges[0] = $minOffset; + } + if ($ind > 0 && $rangeArray[$ind-1]['to']+1 == $ranges[0]) { // case: bytes=500-600,601-999 + $ind--; + $ranges[0] = $rangeArray[$ind]['from']; + } + } + + if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) { + // case: x-x + if ($ranges[1] >= $fileSize) { + $ranges[1] = $fileSize-1; + } + $rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize ]; + $minOffset = $ranges[1] + 1; + if ($minOffset >= $fileSize) { + break; + } + } elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) { + // case: x- + $rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $fileSize-1, 'size' => $fileSize ]; + break; + } elseif (is_numeric($ranges[1])) { + // case: -x + if ($ranges[1] > $fileSize) { + $ranges[1] = $fileSize; + } + $rangeArray[$ind++] = [ 'from' => $fileSize-$ranges[1], 'to' => $fileSize-1, 'size' => $fileSize ]; + break; + } + } + return $rangeArray; + } + + /** + * @param View $view + * @param string $name + * @param string $dir + * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header + */ + private static function getSingleFile($view, $dir, $name, $params) { + $filename = $dir . '/' . $name; + $file = null; + + try { + $userFolder = \OC::$server->getRootFolder()->get(\OC\Files\Filesystem::getRoot()); + $file = $userFolder->get($filename); + if (!$file instanceof \OC\Files\Node\File || !$file->isReadable()) { + http_response_code(403); + die('403 Forbidden'); + } + $fileSize = $file->getSize(); + } catch (\OCP\Files\NotPermittedException $e) { + http_response_code(403); + die('403 Forbidden'); + } catch (\OCP\Files\InvalidPathException $e) { + http_response_code(403); + die('403 Forbidden'); + } catch (\OCP\Files\NotFoundException $e) { + http_response_code(404); + $tmpl = new OC_Template('', '404', 'guest'); + $tmpl->printPage(); + exit(); + } + + OC_Util::obEnd(); + $view->lockFile($filename, ILockingProvider::LOCK_SHARED); + + $rangeArray = []; + + if (isset($params['range']) && substr($params['range'], 0, 6) === 'bytes=') { + $rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), $fileSize); + } + + self::sendHeaders($filename, $name, $rangeArray); + + if (isset($params['head']) && $params['head']) { + return; + } + + if (!empty($rangeArray)) { + try { + if (count($rangeArray) == 1) { + $view->readfilePart($filename, $rangeArray[0]['from'], $rangeArray[0]['to']); + } else { + // check if file is seekable (if not throw UnseekableException) + // we have to check it before body contents + $view->readfilePart($filename, $rangeArray[0]['size'], $rangeArray[0]['size']); + + $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); + + foreach ($rangeArray as $range) { + echo "\r\n--".self::getBoundary()."\r\n". + "Content-type: ".$type."\r\n". + "Content-range: bytes ".$range['from']."-".$range['to']."/".$range['size']."\r\n\r\n"; + $view->readfilePart($filename, $range['from'], $range['to']); + } + echo "\r\n--".self::getBoundary()."--\r\n"; + } + } catch (\OCP\Files\UnseekableException $ex) { + // file is unseekable + header_remove('Accept-Ranges'); + header_remove('Content-Range'); + http_response_code(200); + self::sendHeaders($filename, $name, []); + $view->readfile($filename); + } + } else { + $view->readfile($filename); + } + } + + /** + * Returns the total (recursive) number of files and folders in the given + * FileInfos. + * + * @param \OCP\Files\FileInfo[] $fileInfos the FileInfos to count + * @return int the total number of files and folders + */ + private static function getNumberOfFiles($fileInfos) { + $numberOfFiles = 0; + + $view = new View(); + + while ($fileInfo = array_pop($fileInfos)) { + $numberOfFiles++; + + if ($fileInfo->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) { + $fileInfos = array_merge($fileInfos, $view->getDirectoryContent($fileInfo->getPath())); + } + } + + return $numberOfFiles; + } + + /** + * @param View $view + * @param string $dir + * @param string[]|string $files + */ + public static function lockFiles($view, $dir, $files) { + if (!is_array($files)) { + $file = $dir . '/' . $files; + $files = [$file]; + } + foreach ($files as $file) { + $file = $dir . '/' . $file; + $view->lockFile($file, ILockingProvider::LOCK_SHARED); + if ($view->is_dir($file)) { + $contents = $view->getDirectoryContent($file); + $contents = array_map(function ($fileInfo) use ($file) { + /** @var \OCP\Files\FileInfo $fileInfo */ + return $file . '/' . $fileInfo->getName(); + }, $contents); + self::lockFiles($view, $dir, $contents); + } + } + } + + /** + * @param string $dir + * @param $files + * @param integer $getType + * @param View $view + * @param string $filename + */ + private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) { + if ($getType === self::FILE) { + $view->unlockFile($filename, ILockingProvider::LOCK_SHARED); + } + if ($getType === self::ZIP_FILES) { + foreach ($files as $file) { + $file = $dir . '/' . $file; + $view->unlockFile($file, ILockingProvider::LOCK_SHARED); + } + } + if ($getType === self::ZIP_DIR) { + $file = $dir . '/' . $files; + $view->unlockFile($file, ILockingProvider::LOCK_SHARED); + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Helper.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Helper.php new file mode 100644 index 0000000..8a38ca3 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Helper.php @@ -0,0 +1,605 @@ + + * @author Arthur Schiwon + * @author Bart Visscher + * @author Björn Schießle + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Felix Moeller + * @author Jakob Sack + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Olivier Paroz + * @author Pellaeon Lin + * @author RealRancor + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Simon Könnecke + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use bantu\IniGetWrapper\IniGetWrapper; +use Symfony\Component\Process\ExecutableFinder; + +/** + * Collection of useful functions + */ +class OC_Helper { + private static $templateManager; + + /** + * Make a human file size + * @param int $bytes file size in bytes + * @return string a human readable file size + * + * Makes 2048 to 2 kB. + */ + public static function humanFileSize($bytes) { + if ($bytes < 0) { + return "?"; + } + if ($bytes < 1024) { + return "$bytes B"; + } + $bytes = round($bytes / 1024, 0); + if ($bytes < 1024) { + return "$bytes KB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes MB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes GB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes TB"; + } + + $bytes = round($bytes / 1024, 1); + return "$bytes PB"; + } + + /** + * Make a computer file size + * @param string $str file size in human readable format + * @return float|bool a file size in bytes + * + * Makes 2kB to 2048. + * + * Inspired by: http://www.php.net/manual/en/function.filesize.php#92418 + */ + public static function computerFileSize($str) { + $str = strtolower($str); + if (is_numeric($str)) { + return (float)$str; + } + + $bytes_array = [ + 'b' => 1, + 'k' => 1024, + 'kb' => 1024, + 'mb' => 1024 * 1024, + 'm' => 1024 * 1024, + 'gb' => 1024 * 1024 * 1024, + 'g' => 1024 * 1024 * 1024, + 'tb' => 1024 * 1024 * 1024 * 1024, + 't' => 1024 * 1024 * 1024 * 1024, + 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, + 'p' => 1024 * 1024 * 1024 * 1024 * 1024, + ]; + + $bytes = (float)$str; + + if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) { + $bytes *= $bytes_array[$matches[1]]; + } else { + return false; + } + + $bytes = round($bytes); + + return $bytes; + } + + /** + * Recursive copying of folders + * @param string $src source folder + * @param string $dest target folder + * + */ + public static function copyr($src, $dest) { + if (is_dir($src)) { + if (!is_dir($dest)) { + mkdir($dest); + } + $files = scandir($src); + foreach ($files as $file) { + if ($file != "." && $file != "..") { + self::copyr("$src/$file", "$dest/$file"); + } + } + } elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) { + copy($src, $dest); + } + } + + /** + * Recursive deletion of folders + * @param string $dir path to the folder + * @param bool $deleteSelf if set to false only the content of the folder will be deleted + * @return bool + */ + public static function rmdirr($dir, $deleteSelf = true) { + if (is_dir($dir)) { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileInfo) { + /** @var SplFileInfo $fileInfo */ + if ($fileInfo->isLink()) { + unlink($fileInfo->getPathname()); + } elseif ($fileInfo->isDir()) { + rmdir($fileInfo->getRealPath()); + } else { + unlink($fileInfo->getRealPath()); + } + } + if ($deleteSelf) { + rmdir($dir); + } + } elseif (file_exists($dir)) { + if ($deleteSelf) { + unlink($dir); + } + } + if (!$deleteSelf) { + return true; + } + + return !file_exists($dir); + } + + /** + * @deprecated 18.0.0 + * @return \OC\Files\Type\TemplateManager + */ + public static function getFileTemplateManager() { + if (!self::$templateManager) { + self::$templateManager = new \OC\Files\Type\TemplateManager(); + } + return self::$templateManager; + } + + /** + * detect if a given program is found in the search PATH + * + * @param string $name + * @param bool $path + * @internal param string $program name + * @internal param string $optional search path, defaults to $PATH + * @return bool true if executable program found in path + */ + public static function canExecute($name, $path = false) { + // path defaults to PATH from environment if not set + if ($path === false) { + $path = getenv("PATH"); + } + // we look for an executable file of that name + $exts = [""]; + $check_fn = "is_executable"; + // Default check will be done with $path directories : + $dirs = explode(PATH_SEPARATOR, $path); + // WARNING : We have to check if open_basedir is enabled : + $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir'); + if ($obd != "none") { + $obd_values = explode(PATH_SEPARATOR, $obd); + if (count($obd_values) > 0 and $obd_values[0]) { + // open_basedir is in effect ! + // We need to check if the program is in one of these dirs : + $dirs = $obd_values; + } + } + foreach ($dirs as $dir) { + foreach ($exts as $ext) { + if ($check_fn("$dir/$name" . $ext)) { + return true; + } + } + } + return false; + } + + /** + * copy the contents of one stream to another + * + * @param resource $source + * @param resource $target + * @return array the number of bytes copied and result + */ + public static function streamCopy($source, $target) { + if (!$source or !$target) { + return [0, false]; + } + $bufSize = 8192; + $result = true; + $count = 0; + while (!feof($source)) { + $buf = fread($source, $bufSize); + $bytesWritten = fwrite($target, $buf); + if ($bytesWritten !== false) { + $count += $bytesWritten; + } + // note: strlen is expensive so only use it when necessary, + // on the last block + if ($bytesWritten === false + || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) + ) { + // write error, could be disk full ? + $result = false; + break; + } + } + return [$count, $result]; + } + + /** + * Adds a suffix to the name in case the file exists + * + * @param string $path + * @param string $filename + * @return string + */ + public static function buildNotExistingFileName($path, $filename) { + $view = \OC\Files\Filesystem::getView(); + return self::buildNotExistingFileNameForView($path, $filename, $view); + } + + /** + * Adds a suffix to the name in case the file exists + * + * @param string $path + * @param string $filename + * @return string + */ + public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) { + if ($path === '/') { + $path = ''; + } + if ($pos = strrpos($filename, '.')) { + $name = substr($filename, 0, $pos); + $ext = substr($filename, $pos); + } else { + $name = $filename; + $ext = ''; + } + + $newpath = $path . '/' . $filename; + if ($view->file_exists($newpath)) { + if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) { + //Replace the last "(number)" with "(number+1)" + $last_match = count($matches[0]) - 1; + $counter = $matches[1][$last_match][0] + 1; + $offset = $matches[0][$last_match][1]; + $match_length = strlen($matches[0][$last_match][0]); + } else { + $counter = 2; + $match_length = 0; + $offset = false; + } + do { + if ($offset) { + //Replace the last "(number)" with "(number+1)" + $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length); + } else { + $newname = $name . ' (' . $counter . ')'; + } + $newpath = $path . '/' . $newname . $ext; + $counter++; + } while ($view->file_exists($newpath)); + } + + return $newpath; + } + + /** + * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. + * + * @param array $input The array to work on + * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) + * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 + * @return array + * + * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. + * based on http://www.php.net/manual/en/function.array-change-key-case.php#107715 + * + */ + public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { + $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; + $ret = []; + foreach ($input as $k => $v) { + $ret[mb_convert_case($k, $case, $encoding)] = $v; + } + return $ret; + } + + /** + * performs a search in a nested array + * @param array $haystack the array to be searched + * @param string $needle the search string + * @param mixed $index optional, only search this key name + * @return mixed the key of the matching field, otherwise false + * + * performs a search in a nested array + * + * taken from http://www.php.net/manual/en/function.array-search.php#97645 + */ + public static function recursiveArraySearch($haystack, $needle, $index = null) { + $aIt = new RecursiveArrayIterator($haystack); + $it = new RecursiveIteratorIterator($aIt); + + while ($it->valid()) { + if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) { + return $aIt->key(); + } + + $it->next(); + } + + return false; + } + + /** + * calculates the maximum upload size respecting system settings, free space and user quota + * + * @param string $dir the current folder where the user currently operates + * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly + * @return int number of bytes representing + */ + public static function maxUploadFilesize($dir, $freeSpace = null) { + if (is_null($freeSpace) || $freeSpace < 0) { + $freeSpace = self::freeSpace($dir); + } + return min($freeSpace, self::uploadLimit()); + } + + /** + * Calculate free space left within user quota + * + * @param string $dir the current folder where the user currently operates + * @return int number of bytes representing + */ + public static function freeSpace($dir) { + $freeSpace = \OC\Files\Filesystem::free_space($dir); + if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { + $freeSpace = max($freeSpace, 0); + return $freeSpace; + } else { + return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 + } + } + + /** + * Calculate PHP upload limit + * + * @return int PHP upload file size limit + */ + public static function uploadLimit() { + $ini = \OC::$server->get(IniGetWrapper::class); + $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize')); + $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size')); + if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { + return INF; + } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) { + return max($upload_max_filesize, $post_max_size); //only the non 0 value counts + } else { + return min($upload_max_filesize, $post_max_size); + } + } + + /** + * Checks if a function is available + * + * @param string $function_name + * @return bool + */ + public static function is_function_enabled($function_name) { + if (!function_exists($function_name)) { + return false; + } + $ini = \OC::$server->get(IniGetWrapper::class); + $disabled = explode(',', $ini->get('disable_functions') ?: ''); + $disabled = array_map('trim', $disabled); + if (in_array($function_name, $disabled)) { + return false; + } + $disabled = explode(',', $ini->get('suhosin.executor.func.blacklist') ?: ''); + $disabled = array_map('trim', $disabled); + if (in_array($function_name, $disabled)) { + return false; + } + return true; + } + + /** + * Try to find a program + * + * @param string $program + * @return null|string + */ + public static function findBinaryPath($program) { + $memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath'); + if ($memcache->hasKey($program)) { + return $memcache->get($program); + } + $result = null; + if (self::is_function_enabled('exec')) { + $exeSniffer = new ExecutableFinder(); + // Returns null if nothing is found + $result = $exeSniffer->find($program, null, ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/opt/bin']); + } + // store the value for 5 minutes + $memcache->set($program, $result, 300); + return $result; + } + + /** + * Calculate the disc space for the given path + * + * @param string $path + * @param \OCP\Files\FileInfo $rootInfo (optional) + * @return array + * @throws \OCP\Files\NotFoundException + */ + public static function getStorageInfo($path, $rootInfo = null) { + // return storage info without adding mount points + $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); + + if (!$rootInfo) { + $rootInfo = \OC\Files\Filesystem::getFileInfo($path, $includeExtStorage ? 'ext' : false); + } + if (!$rootInfo instanceof \OCP\Files\FileInfo) { + throw new \OCP\Files\NotFoundException(); + } + $used = $rootInfo->getSize(); + if ($used < 0) { + $used = 0; + } + $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; + $mount = $rootInfo->getMountPoint(); + $storage = $mount->getStorage(); + $sourceStorage = $storage; + if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { + $includeExtStorage = false; + $sourceStorage = $storage->getSourceStorage(); + } + if ($includeExtStorage) { + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') + || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') + ) { + /** @var \OC\Files\Storage\Home $storage */ + $user = $storage->getUser(); + } else { + $user = \OC::$server->getUserSession()->getUser(); + } + $quota = OC_Util::getUserQuota($user); + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { + // always get free space / total space from root + mount points + return self::getGlobalStorageInfo($quota); + } + } + + // TODO: need a better way to get total space from storage + if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) { + /** @var \OC\Files\Storage\Wrapper\Quota $storage */ + $quota = $sourceStorage->getQuota(); + } + $free = $sourceStorage->free_space($rootInfo->getInternalPath()); + if ($free >= 0) { + $total = $free + $used; + } else { + $total = $free; //either unknown or unlimited + } + if ($total > 0) { + if ($quota > 0 && $total > $quota) { + $total = $quota; + } + // prevent division by zero or error codes (negative values) + $relative = round(($used / $total) * 10000) / 100; + } else { + $relative = 0; + } + + $ownerId = $storage->getOwner($path); + $ownerDisplayName = ''; + $owner = \OC::$server->getUserManager()->get($ownerId); + if ($owner) { + $ownerDisplayName = $owner->getDisplayName(); + } + [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); + + return [ + 'free' => $free, + 'used' => $used, + 'quota' => $quota, + 'total' => $total, + 'relative' => $relative, + 'owner' => $ownerId, + 'ownerDisplayName' => $ownerDisplayName, + 'mountType' => $mount->getMountType(), + 'mountPoint' => trim($mountPoint, '/'), + ]; + } + + /** + * Get storage info including all mount points and quota + * + * @param int $quota + * @return array + */ + private static function getGlobalStorageInfo($quota) { + $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); + $used = $rootInfo['size']; + if ($used < 0) { + $used = 0; + } + + $total = $quota; + $free = $quota - $used; + + if ($total > 0) { + if ($quota > 0 && $total > $quota) { + $total = $quota; + } + // prevent division by zero or error codes (negative values) + $relative = round(($used / $total) * 10000) / 100; + } else { + $relative = 0; + } + + return [ + 'free' => $free, + 'used' => $used, + 'total' => $total, + 'relative' => $relative, + 'quota' => $quota + ]; + } + + /** + * Returns whether the config file is set manually to read-only + * @return bool + */ + public static function isReadOnlyConfigEnabled() { + return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Hook.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Hook.php new file mode 100644 index 0000000..1da03df --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Hook.php @@ -0,0 +1,150 @@ + + * @author Christoph Wurst + * @author Jakob Sack + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Sam Tuke + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * @deprecated 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service + */ +class OC_Hook { + public static $thrownExceptions = []; + + private static $registered = []; + + /** + * connects a function to a hook + * + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param string|object $slotClass class name of slot + * @param string $slotName name of slot + * @return bool + * + * This function makes it very easy to connect to use hooks. + * + * TODO: write example + */ + public static function connect($signalClass, $signalName, $slotClass, $slotName) { + // If we're trying to connect to an emitting class that isn't + // yet registered, register it + if (!array_key_exists($signalClass, self::$registered)) { + self::$registered[$signalClass] = []; + } + // If we're trying to connect to an emitting method that isn't + // yet registered, register it with the emitting class + if (!array_key_exists($signalName, self::$registered[$signalClass])) { + self::$registered[$signalClass][$signalName] = []; + } + + // don't connect hooks twice + foreach (self::$registered[$signalClass][$signalName] as $hook) { + if ($hook['class'] === $slotClass and $hook['name'] === $slotName) { + return false; + } + } + // Connect the hook handler to the requested emitter + self::$registered[$signalClass][$signalName][] = [ + "class" => $slotClass, + "name" => $slotName + ]; + + // No chance for failure ;-) + return true; + } + + /** + * emits a signal + * + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param mixed $params default: array() array with additional data + * @return bool true if slots exists or false if not + * @throws \OC\HintException + * @throws \OC\ServerNotAvailableException Emits a signal. To get data from the slot use references! + * + * TODO: write example + */ + public static function emit($signalClass, $signalName, $params = []) { + + // Return false if no hook handlers are listening to this + // emitting class + if (!array_key_exists($signalClass, self::$registered)) { + return false; + } + + // Return false if no hook handlers are listening to this + // emitting method + if (!array_key_exists($signalName, self::$registered[$signalClass])) { + return false; + } + + // Call all slots + foreach (self::$registered[$signalClass][$signalName] as $i) { + try { + call_user_func([ $i["class"], $i["name"] ], $params); + } catch (Exception $e) { + self::$thrownExceptions[] = $e; + \OC::$server->getLogger()->logException($e); + if ($e instanceof \OC\HintException) { + throw $e; + } + if ($e instanceof \OC\ServerNotAvailableException) { + throw $e; + } + } + } + + return true; + } + + /** + * clear hooks + * @param string $signalClass + * @param string $signalName + */ + public static function clear($signalClass='', $signalName='') { + if ($signalClass) { + if ($signalName) { + self::$registered[$signalClass][$signalName]=[]; + } else { + self::$registered[$signalClass]=[]; + } + } else { + self::$registered=[]; + } + } + + /** + * DO NOT USE! + * For unit tests ONLY! + */ + public static function getHooks() { + return self::$registered; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Image.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Image.php new file mode 100644 index 0000000..3e9812c --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Image.php @@ -0,0 +1,1288 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Byron Marohn + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Georg Ehrke + * @author j-ed + * @author Joas Schilling + * @author Johannes Willnecker + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Samuel CHEMLA + * @author Thomas Müller + * @author Thomas Tanghus + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OCP\IImage; + +/** + * Class for basic image manipulation + */ +class OC_Image implements \OCP\IImage { + /** @var false|resource */ + protected $resource = false; // tmp resource. + /** @var int */ + protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. + /** @var string */ + protected $mimeType = 'image/png'; // Default to png + /** @var int */ + protected $bitDepth = 24; + /** @var null|string */ + protected $filePath = null; + /** @var finfo */ + private $fileInfo; + /** @var \OCP\ILogger */ + private $logger; + /** @var \OCP\IConfig */ + private $config; + /** @var array */ + private $exif; + + /** + * Constructor. + * + * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by + * an imagecreate* function. + * @param \OCP\ILogger $logger + * @param \OCP\IConfig $config + * @throws \InvalidArgumentException in case the $imageRef parameter is not null + */ + public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) { + $this->logger = $logger; + if ($logger === null) { + $this->logger = \OC::$server->getLogger(); + } + $this->config = $config; + if ($config === null) { + $this->config = \OC::$server->getConfig(); + } + + if (\OC_Util::fileInfoLoaded()) { + $this->fileInfo = new finfo(FILEINFO_MIME_TYPE); + } + + if ($imageRef !== null) { + throw new \InvalidArgumentException('The first parameter in the constructor is not supported anymore. Please use any of the load* methods of the image object to load an image.'); + } + } + + /** + * Determine whether the object contains an image resource. + * + * @return bool + */ + public function valid() { // apparently you can't name a method 'empty'... + return is_resource($this->resource); + } + + /** + * Returns the MIME type of the image or an empty string if no image is loaded. + * + * @return string + */ + public function mimeType() { + return $this->valid() ? $this->mimeType : ''; + } + + /** + * Returns the width of the image or -1 if no image is loaded. + * + * @return int + */ + public function width() { + return $this->valid() ? imagesx($this->resource) : -1; + } + + /** + * Returns the height of the image or -1 if no image is loaded. + * + * @return int + */ + public function height() { + return $this->valid() ? imagesy($this->resource) : -1; + } + + /** + * Returns the width when the image orientation is top-left. + * + * @return int + */ + public function widthTopLeft() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['app' => 'core']); + switch ($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->width(); + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->height(); + } + return $this->width(); + } + + /** + * Returns the height when the image orientation is top-left. + * + * @return int + */ + public function heightTopLeft() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']); + switch ($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->height(); + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->width(); + } + return $this->height(); + } + + /** + * Outputs the image. + * + * @param string $mimeType + * @return bool + */ + public function show($mimeType = null) { + if ($mimeType === null) { + $mimeType = $this->mimeType(); + } + header('Content-Type: ' . $mimeType); + return $this->_output(null, $mimeType); + } + + /** + * Saves the image. + * + * @param string $filePath + * @param string $mimeType + * @return bool + */ + + public function save($filePath = null, $mimeType = null) { + if ($mimeType === null) { + $mimeType = $this->mimeType(); + } + if ($filePath === null) { + if ($this->filePath === null) { + $this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']); + return false; + } else { + $filePath = $this->filePath; + } + } + return $this->_output($filePath, $mimeType); + } + + /** + * Outputs/saves the image. + * + * @param string $filePath + * @param string $mimeType + * @return bool + * @throws Exception + */ + private function _output($filePath = null, $mimeType = null) { + if ($filePath) { + if (!file_exists(dirname($filePath))) { + mkdir(dirname($filePath), 0777, true); + } + $isWritable = is_writable(dirname($filePath)); + if (!$isWritable) { + $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']); + return false; + } elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) { + $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']); + return false; + } + } + if (!$this->valid()) { + return false; + } + + $imageType = $this->imageType; + if ($mimeType !== null) { + switch ($mimeType) { + case 'image/gif': + $imageType = IMAGETYPE_GIF; + break; + case 'image/jpeg': + $imageType = IMAGETYPE_JPEG; + break; + case 'image/png': + $imageType = IMAGETYPE_PNG; + break; + case 'image/x-xbitmap': + $imageType = IMAGETYPE_XBM; + break; + case 'image/bmp': + case 'image/x-ms-bmp': + $imageType = IMAGETYPE_BMP; + break; + default: + throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format'); + } + } + + switch ($imageType) { + case IMAGETYPE_GIF: + $retVal = imagegif($this->resource, $filePath); + break; + case IMAGETYPE_JPEG: + $retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality()); + break; + case IMAGETYPE_PNG: + $retVal = imagepng($this->resource, $filePath); + break; + case IMAGETYPE_XBM: + if (function_exists('imagexbm')) { + $retVal = imagexbm($this->resource, $filePath); + } else { + throw new Exception('\OC_Image::_output(): imagexbm() is not supported.'); + } + + break; + case IMAGETYPE_WBMP: + $retVal = imagewbmp($this->resource, $filePath); + break; + case IMAGETYPE_BMP: + $retVal = imagebmp($this->resource, $filePath, $this->bitDepth); + break; + default: + $retVal = imagepng($this->resource, $filePath); + } + return $retVal; + } + + /** + * Prints the image when called as $image(). + */ + public function __invoke() { + return $this->show(); + } + + /** + * @param resource Returns the image resource in any. + * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd" + */ + public function setResource($resource) { + if (get_resource_type($resource) === 'gd') { + $this->resource = $resource; + return; + } + throw new \InvalidArgumentException('Supplied resource is not of type "gd".'); + } + + /** + * @return resource Returns the image resource in any. + */ + public function resource() { + return $this->resource; + } + + /** + * @return string Returns the mimetype of the data. Returns the empty string + * if the data is not valid. + */ + public function dataMimeType() { + if (!$this->valid()) { + return ''; + } + + switch ($this->mimeType) { + case 'image/png': + case 'image/jpeg': + case 'image/gif': + return $this->mimeType; + default: + return 'image/png'; + } + } + + /** + * @return null|string Returns the raw image data. + */ + public function data() { + if (!$this->valid()) { + return null; + } + ob_start(); + switch ($this->mimeType) { + case "image/png": + $res = imagepng($this->resource); + break; + case "image/jpeg": + $quality = $this->getJpegQuality(); + if ($quality !== null) { + $res = imagejpeg($this->resource, null, $quality); + } else { + $res = imagejpeg($this->resource); + } + break; + case "image/gif": + $res = imagegif($this->resource); + break; + default: + $res = imagepng($this->resource); + $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', ['app' => 'core']); + break; + } + if (!$res) { + $this->logger->error('OC_Image->data. Error getting image data.', ['app' => 'core']); + } + return ob_get_clean(); + } + + /** + * @return string - base64 encoded, which is suitable for embedding in a VCard. + */ + public function __toString() { + return base64_encode($this->data()); + } + + /** + * @return int|null + */ + protected function getJpegQuality() { + $quality = $this->config->getAppValue('preview', 'jpeg_quality', 90); + if ($quality !== null) { + $quality = min(100, max(10, (int) $quality)); + } + return $quality; + } + + /** + * (I'm open for suggestions on better method name ;) + * Get the orientation based on EXIF data. + * + * @return int The orientation or -1 if no EXIF data is available. + */ + public function getOrientation() { + if ($this->exif !== null) { + return $this->exif['Orientation']; + } + + if ($this->imageType !== IMAGETYPE_JPEG) { + $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', ['app' => 'core']); + return -1; + } + if (!is_callable('exif_read_data')) { + $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']); + return -1; + } + if (!$this->valid()) { + $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']); + return -1; + } + if (is_null($this->filePath) || !is_readable($this->filePath)) { + $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', ['app' => 'core']); + return -1; + } + $exif = @exif_read_data($this->filePath, 'IFD0'); + if (!$exif) { + return -1; + } + if (!isset($exif['Orientation'])) { + return -1; + } + $this->exif = $exif; + return $exif['Orientation']; + } + + public function readExif($data) { + if (!is_callable('exif_read_data')) { + $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']); + return; + } + if (!$this->valid()) { + $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']); + return; + } + + $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data)); + if (!$exif) { + return; + } + if (!isset($exif['Orientation'])) { + return; + } + $this->exif = $exif; + } + + /** + * (I'm open for suggestions on better method name ;) + * Fixes orientation based on EXIF data. + * + * @return bool + */ + public function fixOrientation() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, ['app' => 'core']); + $rotate = 0; + $flip = false; + switch ($o) { + case -1: + return false; //Nothing to fix + case 1: + $rotate = 0; + break; + case 2: + $rotate = 0; + $flip = true; + break; + case 3: + $rotate = 180; + break; + case 4: + $rotate = 180; + $flip = true; + break; + case 5: + $rotate = 90; + $flip = true; + break; + case 6: + $rotate = 270; + break; + case 7: + $rotate = 270; + $flip = true; + break; + case 8: + $rotate = 90; + break; + } + if ($flip && function_exists('imageflip')) { + imageflip($this->resource, IMG_FLIP_HORIZONTAL); + } + if ($rotate) { + $res = imagerotate($this->resource, $rotate, 0); + if ($res) { + if (imagealphablending($res, true)) { + if (imagesavealpha($res, true)) { + imagedestroy($this->resource); + $this->resource = $res; + return true; + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']); + return false; + } + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']); + return false; + } + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']); + return false; + } + } + return false; + } + + /** + * Loads an image from an open file handle. + * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again. + * + * @param resource $handle + * @return resource|false An image resource or false on error + */ + public function loadFromFileHandle($handle) { + $contents = stream_get_contents($handle); + if ($this->loadFromData($contents)) { + return $this->resource; + } + return false; + } + + /** + * Loads an image from a local file. + * + * @param bool|string $imagePath The path to a local file. + * @return bool|resource An image resource or false on error + */ + public function loadFromFile($imagePath = false) { + // exif_imagetype throws "read error!" if file is less than 12 byte + if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) { + return false; + } + $iType = exif_imagetype($imagePath); + switch ($iType) { + case IMAGETYPE_GIF: + if (imagetypes() & IMG_GIF) { + $this->resource = imagecreatefromgif($imagePath); + // Preserve transparency + imagealphablending($this->resource, true); + imagesavealpha($this->resource, true); + } else { + $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, ['app' => 'core']); + } + break; + case IMAGETYPE_JPEG: + if (imagetypes() & IMG_JPG) { + if (getimagesize($imagePath) !== false) { + $this->resource = @imagecreatefromjpeg($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, ['app' => 'core']); + } + } else { + $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, ['app' => 'core']); + } + break; + case IMAGETYPE_PNG: + if (imagetypes() & IMG_PNG) { + $this->resource = @imagecreatefrompng($imagePath); + // Preserve transparency + imagealphablending($this->resource, true); + imagesavealpha($this->resource, true); + } else { + $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, ['app' => 'core']); + } + break; + case IMAGETYPE_XBM: + if (imagetypes() & IMG_XPM) { + $this->resource = @imagecreatefromxbm($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']); + } + break; + case IMAGETYPE_WBMP: + if (imagetypes() & IMG_WBMP) { + $this->resource = @imagecreatefromwbmp($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']); + } + break; + case IMAGETYPE_BMP: + $this->resource = $this->imagecreatefrombmp($imagePath); + break; + /* + case IMAGETYPE_TIFF_II: // (intel byte order) + break; + case IMAGETYPE_TIFF_MM: // (motorola byte order) + break; + case IMAGETYPE_JPC: + break; + case IMAGETYPE_JP2: + break; + case IMAGETYPE_JPX: + break; + case IMAGETYPE_JB2: + break; + case IMAGETYPE_SWC: + break; + case IMAGETYPE_IFF: + break; + case IMAGETYPE_ICO: + break; + case IMAGETYPE_SWF: + break; + case IMAGETYPE_PSD: + break; + */ + default: + + // this is mostly file created from encrypted file + $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath))); + $iType = IMAGETYPE_PNG; + $this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']); + break; + } + if ($this->valid()) { + $this->imageType = $iType; + $this->mimeType = image_type_to_mime_type($iType); + $this->filePath = $imagePath; + } + return $this->resource; + } + + /** + * Loads an image from a string of data. + * + * @param string $str A string of image data as read from a file. + * @return bool|resource An image resource or false on error + */ + public function loadFromData($str) { + if (is_resource($str)) { + return false; + } + $this->resource = @imagecreatefromstring($str); + if ($this->fileInfo) { + $this->mimeType = $this->fileInfo->buffer($str); + } + if (is_resource($this->resource)) { + imagealphablending($this->resource, false); + imagesavealpha($this->resource, true); + } + + if (!$this->resource) { + $this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']); + return false; + } + return $this->resource; + } + + /** + * Loads an image from a base64 encoded string. + * + * @param string $str A string base64 encoded string of image data. + * @return bool|resource An image resource or false on error + */ + public function loadFromBase64($str) { + if (!is_string($str)) { + return false; + } + $data = base64_decode($str); + if ($data) { // try to load from string data + $this->resource = @imagecreatefromstring($data); + if ($this->fileInfo) { + $this->mimeType = $this->fileInfo->buffer($data); + } + if (!$this->resource) { + $this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']); + return false; + } + return $this->resource; + } else { + return false; + } + } + + /** + * Create a new image from file or URL + * + * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm + * @version 1.00 + * @param string $fileName

+ * Path to the BMP image. + *

+ * @return bool|resource an image resource identifier on success, FALSE on errors. + */ + private function imagecreatefrombmp($fileName) { + if (!($fh = fopen($fileName, 'rb'))) { + $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']); + return false; + } + // read file header + $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); + // check for bitmap + if ($meta['type'] != 19778) { + fclose($fh); + $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']); + return false; + } + // read image header + $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40)); + // read additional 16bit header + if ($meta['bits'] == 16) { + $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12)); + } + // set bytes and padding + $meta['bytes'] = $meta['bits'] / 8; + $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call + $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4))); + if ($meta['decal'] == 4) { + $meta['decal'] = 0; + } + // obtain imagesize + if ($meta['imagesize'] < 1) { + $meta['imagesize'] = $meta['filesize'] - $meta['offset']; + // in rare cases filesize is equal to offset so we need to read physical size + if ($meta['imagesize'] < 1) { + $meta['imagesize'] = @filesize($fileName) - $meta['offset']; + if ($meta['imagesize'] < 1) { + fclose($fh); + $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']); + return false; + } + } + } + // calculate colors + $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors']; + // read color palette + $palette = []; + if ($meta['bits'] < 16) { + $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4)); + // in rare cases the color value is signed + if ($palette[1] < 0) { + foreach ($palette as $i => $color) { + $palette[$i] = $color + 16777216; + } + } + } + // create gd image + $im = imagecreatetruecolor($meta['width'], $meta['height']); + if ($im == false) { + fclose($fh); + $this->logger->warning( + 'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'], + ['app' => 'core']); + return false; + } + + $data = fread($fh, $meta['imagesize']); + $p = 0; + $vide = chr(0); + $y = $meta['height'] - 1; + $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!'; + // loop through the image data beginning with the lower left corner + while ($y >= 0) { + $x = 0; + while ($x < $meta['width']) { + switch ($meta['bits']) { + case 32: + case 24: + if (!($part = substr($data, $p, 3))) { + $this->logger->warning($error, ['app' => 'core']); + return $im; + } + $color = @unpack('V', $part . $vide); + break; + case 16: + if (!($part = substr($data, $p, 2))) { + fclose($fh); + $this->logger->warning($error, ['app' => 'core']); + return $im; + } + $color = @unpack('v', $part); + $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); + break; + case 8: + $color = @unpack('n', $vide . ($data[$p] ?? '')); + $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1]; + break; + case 4: + $color = @unpack('n', $vide . ($data[floor($p)] ?? '')); + $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F; + $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1]; + break; + case 1: + $color = @unpack('n', $vide . ($data[floor($p)] ?? '')); + switch (($p * 8) % 8) { + case 0: + $color[1] = $color[1] >> 7; + break; + case 1: + $color[1] = ($color[1] & 0x40) >> 6; + break; + case 2: + $color[1] = ($color[1] & 0x20) >> 5; + break; + case 3: + $color[1] = ($color[1] & 0x10) >> 4; + break; + case 4: + $color[1] = ($color[1] & 0x8) >> 3; + break; + case 5: + $color[1] = ($color[1] & 0x4) >> 2; + break; + case 6: + $color[1] = ($color[1] & 0x2) >> 1; + break; + case 7: + $color[1] = ($color[1] & 0x1); + break; + } + $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1]; + break; + default: + fclose($fh); + $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']); + return false; + } + imagesetpixel($im, $x, $y, $color[1]); + $x++; + $p += $meta['bytes']; + } + $y--; + $p += $meta['decal']; + } + fclose($fh); + return $im; + } + + /** + * Resizes the image preserving ratio. + * + * @param integer $maxSize The maximum size of either the width or height. + * @return bool + */ + public function resize($maxSize) { + $result = $this->resizeNew($maxSize); + imagedestroy($this->resource); + $this->resource = $result; + return is_resource($result); + } + + /** + * @param $maxSize + * @return resource | bool + */ + private function resizeNew($maxSize) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']); + return false; + } + $widthOrig = imagesx($this->resource); + $heightOrig = imagesy($this->resource); + $ratioOrig = $widthOrig / $heightOrig; + + if ($ratioOrig > 1) { + $newHeight = round($maxSize / $ratioOrig); + $newWidth = $maxSize; + } else { + $newWidth = round($maxSize * $ratioOrig); + $newHeight = $maxSize; + } + + return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight)); + } + + /** + * @param int $width + * @param int $height + * @return bool + */ + public function preciseResize(int $width, int $height): bool { + $result = $this->preciseResizeNew($width, $height); + imagedestroy($this->resource); + $this->resource = $result; + return is_resource($result); + } + + + /** + * @param int $width + * @param int $height + * @return resource | bool + */ + public function preciseResizeNew(int $width, int $height) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']); + return false; + } + $widthOrig = imagesx($this->resource); + $heightOrig = imagesy($this->resource); + $process = imagecreatetruecolor($width, $height); + if ($process === false) { + $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + $res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); + if ($res === false) { + $this->logger->error(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']); + imagedestroy($process); + return false; + } + return $process; + } + + /** + * Crops the image to the middle square. If the image is already square it just returns. + * + * @param int $size maximum size for the result (optional) + * @return bool for success or failure + */ + public function centerCrop($size = 0) { + if (!$this->valid()) { + $this->logger->error('OC_Image->centerCrop, No image loaded', ['app' => 'core']); + return false; + } + $widthOrig = imagesx($this->resource); + $heightOrig = imagesy($this->resource); + if ($widthOrig === $heightOrig and $size == 0) { + return true; + } + $ratioOrig = $widthOrig / $heightOrig; + $width = $height = min($widthOrig, $heightOrig); + + if ($ratioOrig > 1) { + $x = ($widthOrig / 2) - ($width / 2); + $y = 0; + } else { + $y = ($heightOrig / 2) - ($height / 2); + $x = 0; + } + if ($size > 0) { + $targetWidth = $size; + $targetHeight = $size; + } else { + $targetWidth = $width; + $targetHeight = $height; + } + $process = imagecreatetruecolor($targetWidth, $targetHeight); + if ($process == false) { + $this->logger->error('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']); + imagedestroy($process); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); + if ($process == false) { + $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * Crops the image from point $x$y with dimension $wx$h. + * + * @param int $x Horizontal position + * @param int $y Vertical position + * @param int $w Width + * @param int $h Height + * @return bool for success or failure + */ + public function crop(int $x, int $y, int $w, int $h): bool { + $result = $this->cropNew($x, $y, $w, $h); + imagedestroy($this->resource); + $this->resource = $result; + return is_resource($result); + } + + /** + * Crops the image from point $x$y with dimension $wx$h. + * + * @param int $x Horizontal position + * @param int $y Vertical position + * @param int $w Width + * @param int $h Height + * @return resource | bool + */ + public function cropNew(int $x, int $y, int $w, int $h) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']); + return false; + } + $process = imagecreatetruecolor($w, $h); + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']); + imagedestroy($process); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']); + imagedestroy($process); + return false; + } + return $process; + } + + /** + * Resizes the image to fit within a boundary while preserving ratio. + * + * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up + * + * @param integer $maxWidth + * @param integer $maxHeight + * @return bool + */ + public function fitIn($maxWidth, $maxHeight) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']); + return false; + } + $widthOrig = imagesx($this->resource); + $heightOrig = imagesy($this->resource); + $ratio = $widthOrig / $heightOrig; + + $newWidth = min($maxWidth, $ratio * $maxHeight); + $newHeight = min($maxHeight, $maxWidth / $ratio); + + $this->preciseResize((int)round($newWidth), (int)round($newHeight)); + return true; + } + + /** + * Shrinks larger images to fit within specified boundaries while preserving ratio. + * + * @param integer $maxWidth + * @param integer $maxHeight + * @return bool + */ + public function scaleDownToFit($maxWidth, $maxHeight) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']); + return false; + } + $widthOrig = imagesx($this->resource); + $heightOrig = imagesy($this->resource); + + if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) { + return $this->fitIn($maxWidth, $maxHeight); + } + + return false; + } + + public function copy(): IImage { + $image = new OC_Image(null, $this->logger, $this->config); + $image->resource = imagecreatetruecolor($this->width(), $this->height()); + imagecopy( + $image->resource(), + $this->resource(), + 0, + 0, + 0, + 0, + $this->width(), + $this->height() + ); + + return $image; + } + + public function cropCopy(int $x, int $y, int $w, int $h): IImage { + $image = new OC_Image(null, $this->logger, $this->config); + $image->imageType = $this->imageType; + $image->mimeType = $this->mimeType; + $image->bitDepth = $this->bitDepth; + $image->resource = $this->cropNew($x, $y, $w, $h); + + return $image; + } + + public function preciseResizeCopy(int $width, int $height): IImage { + $image = new OC_Image(null, $this->logger, $this->config); + $image->imageType = $this->imageType; + $image->mimeType = $this->mimeType; + $image->bitDepth = $this->bitDepth; + $image->resource = $this->preciseResizeNew($width, $height); + + return $image; + } + + public function resizeCopy(int $maxSize): IImage { + $image = new OC_Image(null, $this->logger, $this->config); + $image->imageType = $this->imageType; + $image->mimeType = $this->mimeType; + $image->bitDepth = $this->bitDepth; + $image->resource = $this->resizeNew($maxSize); + + return $image; + } + + + /** + * Resizes the image preserving ratio, returning a new copy + * + * @param integer $maxSize The maximum size of either the width or height. + * @return bool + */ + public function copyResize($maxSize): IImage { + } + + /** + * Destroys the current image and resets the object + */ + public function destroy() { + if ($this->valid()) { + imagedestroy($this->resource); + } + $this->resource = null; + } + + public function __destruct() { + $this->destroy(); + } +} + +if (!function_exists('imagebmp')) { + /** + * Output a BMP image to either the browser or a file + * + * @link http://www.ugia.cn/wp-data/imagebmp.php + * @author legend + * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm + * @author mgutt + * @version 1.00 + * @param resource $im + * @param string $fileName [optional]

The path to save the file to.

+ * @param int $bit [optional]

Bit depth, (default is 24).

+ * @param int $compression [optional] + * @return bool TRUE on success or FALSE on failure. + */ + function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) { + if (!in_array($bit, [1, 4, 8, 16, 24, 32])) { + $bit = 24; + } elseif ($bit == 32) { + $bit = 24; + } + $bits = (int)pow(2, $bit); + imagetruecolortopalette($im, true, $bits); + $width = imagesx($im); + $height = imagesy($im); + $colorsNum = imagecolorstotal($im); + $rgbQuad = ''; + if ($bit <= 8) { + for ($i = 0; $i < $colorsNum; $i++) { + $colors = imagecolorsforindex($im, $i); + $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0"; + } + $bmpData = ''; + if ($compression == 0 || $bit < 8) { + $compression = 0; + $extra = ''; + $padding = 4 - ceil($width / (8 / $bit)) % 4; + if ($padding % 4 != 0) { + $extra = str_repeat("\0", $padding); + } + for ($j = $height - 1; $j >= 0; $j--) { + $i = 0; + while ($i < $width) { + $bin = 0; + $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0; + for ($k = 8 - $bit; $k >= $limit; $k -= $bit) { + $index = imagecolorat($im, $i, $j); + $bin |= $index << $k; + $i++; + } + $bmpData .= chr($bin); + } + $bmpData .= $extra; + } + } // RLE8 + elseif ($compression == 1 && $bit == 8) { + for ($j = $height - 1; $j >= 0; $j--) { + $lastIndex = null; + $sameNum = 0; + for ($i = 0; $i <= $width; $i++) { + $index = imagecolorat($im, $i, $j); + if ($index !== $lastIndex || $sameNum > 255) { + if ($sameNum != 0) { + $bmpData .= chr($sameNum) . chr($lastIndex); + } + $lastIndex = $index; + $sameNum = 1; + } else { + $sameNum++; + } + } + $bmpData .= "\0\0"; + } + $bmpData .= "\0\1"; + } + $sizeQuad = strlen($rgbQuad); + $sizeData = strlen($bmpData); + } else { + $extra = ''; + $padding = 4 - ($width * ($bit / 8)) % 4; + if ($padding % 4 != 0) { + $extra = str_repeat("\0", $padding); + } + $bmpData = ''; + for ($j = $height - 1; $j >= 0; $j--) { + for ($i = 0; $i < $width; $i++) { + $index = imagecolorat($im, $i, $j); + $colors = imagecolorsforindex($im, $index); + if ($bit == 16) { + $bin = 0 << $bit; + $bin |= ($colors['red'] >> 3) << 10; + $bin |= ($colors['green'] >> 3) << 5; + $bin |= $colors['blue'] >> 3; + $bmpData .= pack("v", $bin); + } else { + $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']); + } + } + $bmpData .= $extra; + } + $sizeQuad = 0; + $sizeData = strlen($bmpData); + $colorsNum = 0; + } + $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad); + $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0); + if ($fileName != '') { + $fp = fopen($fileName, 'wb'); + fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData); + fclose($fp); + return true; + } + echo $fileHeader . $infoHeader . $rgbQuad . $bmpData; + return true; + } +} + +if (!function_exists('exif_imagetype')) { + /** + * Workaround if exif_imagetype does not exist + * + * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383 + * @param string $fileName + * @return string|boolean + */ + function exif_imagetype($fileName) { + if (($info = getimagesize($fileName)) !== false) { + return $info[2]; + } + return false; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_JSON.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_JSON.php new file mode 100644 index 0000000..a0b9868 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_JSON.php @@ -0,0 +1,139 @@ + + * @author Christoph Wurst + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class OC_JSON + * @deprecated Use a AppFramework JSONResponse instead + */ +class OC_JSON { + + /** + * Check if the app is enabled, send json error msg if not + * @param string $app + * @deprecated Use the AppFramework instead. It will automatically check if the app is enabled. + * @suppress PhanDeprecatedFunction + */ + public static function checkAppEnabled($app) { + if (!\OC::$server->getAppManager()->isEnabledForUser($app)) { + $l = \OC::$server->getL10N('lib'); + self::error([ 'data' => [ 'message' => $l->t('Application is not enabled'), 'error' => 'application_not_enabled' ]]); + exit(); + } + } + + /** + * Check if the user is logged in, send json error msg if not + * @deprecated Use annotation based ACLs from the AppFramework instead + * @suppress PhanDeprecatedFunction + */ + public static function checkLoggedIn() { + $twoFactorAuthManger = \OC::$server->getTwoFactorAuthManager(); + if (!\OC::$server->getUserSession()->isLoggedIn() + || $twoFactorAuthManger->needsSecondFactor(\OC::$server->getUserSession()->getUser())) { + $l = \OC::$server->getL10N('lib'); + http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); + self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]); + exit(); + } + } + + /** + * Check an ajax get/post call if the request token is valid, send json error msg if not. + * @deprecated Use annotation based CSRF checks from the AppFramework instead + * @suppress PhanDeprecatedFunction + */ + public static function callCheck() { + if (!\OC::$server->getRequest()->passesStrictCookieCheck()) { + header('Location: '.\OC::$WEBROOT); + exit(); + } + + if (!\OC::$server->getRequest()->passesCSRFCheck()) { + $l = \OC::$server->getL10N('lib'); + self::error([ 'data' => [ 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ]]); + exit(); + } + } + + /** + * Check if the user is a admin, send json error msg if not. + * @deprecated Use annotation based ACLs from the AppFramework instead + * @suppress PhanDeprecatedFunction + */ + public static function checkAdminUser() { + if (!OC_User::isAdminUser(OC_User::getUser())) { + $l = \OC::$server->getL10N('lib'); + self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]); + exit(); + } + } + + /** + * Send json error msg + * @deprecated Use a AppFramework JSONResponse instead + * @suppress PhanDeprecatedFunction + */ + public static function error($data = []) { + $data['status'] = 'error'; + header('Content-Type: application/json; charset=utf-8'); + echo self::encode($data); + } + + /** + * Send json success msg + * @deprecated Use a AppFramework JSONResponse instead + * @suppress PhanDeprecatedFunction + */ + public static function success($data = []) { + $data['status'] = 'success'; + header('Content-Type: application/json; charset=utf-8'); + echo self::encode($data); + } + + /** + * Convert OC_L10N_String to string, for use in json encodings + */ + protected static function to_string(&$value) { + if ($value instanceof \OC\L10N\L10NString) { + $value = (string)$value; + } + } + + /** + * Encode JSON + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function encode($data) { + if (is_array($data)) { + array_walk_recursive($data, ['OC_JSON', 'to_string']); + } + return json_encode($data, JSON_HEX_TAG); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Response.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Response.php new file mode 100644 index 0000000..491c691 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Response.php @@ -0,0 +1,108 @@ + + * @author Bart Visscher + * @author Christoph Wurst + * @author J0WI + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +class OC_Response { + /** + * Sets the content disposition header (with possible workarounds) + * @param string $filename file name + * @param string $type disposition type, either 'attachment' or 'inline' + */ + public static function setContentDispositionHeader($filename, $type = 'attachment') { + if (\OC::$server->getRequest()->isUserAgent( + [ + \OC\AppFramework\Http\Request::USER_AGENT_IE, + \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, + \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, + ])) { + header('Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode($filename) . '"'); + } else { + header('Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode($filename) + . '; filename="' . rawurlencode($filename) . '"'); + } + } + + /** + * Sets the content length header (with possible workarounds) + * @param string|int|float $length Length to be sent + */ + public static function setContentLengthHeader($length) { + if (PHP_INT_SIZE === 4) { + if ($length > PHP_INT_MAX && stripos(PHP_SAPI, 'apache') === 0) { + // Apache PHP SAPI casts Content-Length headers to PHP integers. + // This enforces a limit of PHP_INT_MAX (2147483647 on 32-bit + // platforms). So, if the length is greater than PHP_INT_MAX, + // we just do not send a Content-Length header to prevent + // bodies from being received incompletely. + return; + } + // Convert signed integer or float to unsigned base-10 string. + $lfh = new \OC\LargeFileHelper; + $length = $lfh->formatUnsignedInteger($length); + } + header('Content-Length: '.$length); + } + + /** + * This function adds some security related headers to all requests served via base.php + * The implementation of this function has to happen here to ensure that all third-party + * components (e.g. SabreDAV) also benefit from this headers. + */ + public static function addSecurityHeaders() { + /** + * FIXME: Content Security Policy for legacy ownCloud components. This + * can be removed once \OCP\AppFramework\Http\Response from the AppFramework + * is used everywhere. + * @see \OCP\AppFramework\Http\Response::getHeaders + */ + $policy = 'default-src \'self\'; ' + . 'script-src \'self\' \'nonce-'.\OC::$server->getContentSecurityPolicyNonceManager()->getNonce().'\'; ' + . 'style-src \'self\' \'unsafe-inline\'; ' + . 'frame-src *; ' + . 'img-src * data: blob:; ' + . 'font-src \'self\' data:; ' + . 'media-src *; ' + . 'connect-src *; ' + . 'object-src \'none\'; ' + . 'base-uri \'self\'; '; + header('Content-Security-Policy:' . $policy); + + // Send fallback headers for installations that don't have the possibility to send + // custom headers on the webserver side + if (getenv('modHeadersAvailable') !== 'true') { + header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/ + header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE + header('X-Download-Options: noopen'); // https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx + header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains + header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag + header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters + } + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Template.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Template.php new file mode 100644 index 0000000..54c203a --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Template.php @@ -0,0 +1,358 @@ + + * @author Brice Maron + * @author Christoph Wurst + * @author Frank Karlitschek + * @author Individual IT Services + * @author Jakob Sack + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Marin Treselj + * @author Michael Letzgus + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OC\TemplateLayout; +use OCP\AppFramework\Http\TemplateResponse; + +require_once __DIR__.'/template/functions.php'; + +/** + * This class provides the templates for ownCloud. + */ +class OC_Template extends \OC\Template\Base { + + /** @var string */ + private $renderAs; // Create a full page? + + /** @var string */ + private $path; // The path to the template + + /** @var array */ + private $headers = []; //custom headers + + /** @var string */ + protected $app; // app id + + protected static $initTemplateEngineFirstRun = true; + + /** + * Constructor + * + * @param string $app app providing the template + * @param string $name of the template file (without suffix) + * @param string $renderAs If $renderAs is set, OC_Template will try to + * produce a full page in the according layout. For + * now, $renderAs can be set to "guest", "user" or + * "admin". + * @param bool $registerCall = true + */ + public function __construct($app, $name, $renderAs = TemplateResponse::RENDER_AS_BLANK, $registerCall = true) { + // Read the selected theme from the config file + self::initTemplateEngine($renderAs); + + $theme = OC_Util::getTheme(); + + $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : ''; + + $parts = explode('/', $app); // fix translation when app is something like core/lostpassword + $l10n = \OC::$server->getL10N($parts[0]); + /** @var \OCP\Defaults $themeDefaults */ + $themeDefaults = \OC::$server->query(\OCP\Defaults::class); + + list($path, $template) = $this->findTemplate($theme, $app, $name); + + // Set the private data + $this->renderAs = $renderAs; + $this->path = $path; + $this->app = $app; + + parent::__construct($template, $requestToken, $l10n, $themeDefaults); + } + + /** + * @param string $renderAs + */ + public static function initTemplateEngine($renderAs) { + if (self::$initTemplateEngineFirstRun) { + + //apps that started before the template initialization can load their own scripts/styles + //so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true + //meaning the last script/style in this list will be loaded first + if (\OC::$server->getSystemConfig()->getValue('installed', false) && $renderAs !== TemplateResponse::RENDER_AS_ERROR && !\OCP\Util::needUpgrade()) { + if (\OC::$server->getConfig()->getAppValue('core', 'backgroundjobs_mode', 'ajax') == 'ajax') { + OC_Util::addScript('backgroundjobs', null, true); + } + } + OC_Util::addStyle('css-variables', null, true); + OC_Util::addStyle('server', null, true); + OC_Util::addTranslations('core', null, true); + + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + OC_Util::addScript('merged-template-prepend', null, true); + OC_Util::addScript('dist/files_client', null, true); + OC_Util::addScript('dist/files_fileinfo', null, true); + } + OC_Util::addScript('core', 'dist/main', true); + + if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) { + // shim for the davclient.js library + \OCP\Util::addScript('dist/files_iedavclient'); + } + + self::$initTemplateEngineFirstRun = false; + } + } + + + /** + * find the template with the given name + * @param string $name of the template file (without suffix) + * + * Will select the template file for the selected theme. + * Checking all the possible locations. + * @param string $theme + * @param string $app + * @return string[] + */ + protected function findTemplate($theme, $app, $name) { + // Check if it is a app template or not. + if ($app !== '') { + $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app)); + } else { + $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT); + } + $locator = new \OC\Template\TemplateFileLocator($dirs); + $template = $locator->find($name); + $path = $locator->getPath(); + return [$path, $template]; + } + + /** + * Add a custom element to the header + * @param string $tag tag name of the element + * @param array $attributes array of attributes for the element + * @param string $text the text content for the element. If $text is null then the + * element will be written as empty element. So use "" to get a closing tag. + */ + public function addHeader($tag, $attributes, $text=null) { + $this->headers[]= [ + 'tag' => $tag, + 'attributes' => $attributes, + 'text' => $text + ]; + } + + /** + * Process the template + * @return string + * + * This function process the template. If $this->renderAs is set, it + * will produce a full page. + */ + public function fetchPage($additionalParams = null) { + $data = parent::fetchPage($additionalParams); + + if ($this->renderAs) { + $page = new TemplateLayout($this->renderAs, $this->app); + + if (is_array($additionalParams)) { + foreach ($additionalParams as $key => $value) { + $page->assign($key, $value); + } + } + + // Add custom headers + $headers = ''; + foreach (OC_Util::$headers as $header) { + $headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']); + if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) { + $headers .= ' defer'; + } + foreach ($header['attributes'] as $name=>$value) { + $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"'; + } + if ($header['text'] !== null) { + $headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).''; + } else { + $headers .= '/>'; + } + } + + $page->assign('headers', $headers); + + $page->assign('content', $data); + return $page->fetchPage($additionalParams); + } + + return $data; + } + + /** + * Include template + * + * @param string $file + * @param array|null $additionalParams + * @return string returns content of included template + * + * Includes another template. use inc('template'); ?> to + * do this. + */ + public function inc($file, $additionalParams = null) { + return $this->load($this->path.$file.'.php', $additionalParams); + } + + /** + * Shortcut to print a simple page for users + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array $parameters Parameters for the template + * @return boolean|null + */ + public static function printUserPage($application, $name, $parameters = []) { + $content = new OC_Template($application, $name, "user"); + foreach ($parameters as $key => $value) { + $content->assign($key, $value); + } + print $content->printPage(); + } + + /** + * Shortcut to print a simple page for admins + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array $parameters Parameters for the template + * @return bool + */ + public static function printAdminPage($application, $name, $parameters = []) { + $content = new OC_Template($application, $name, "admin"); + foreach ($parameters as $key => $value) { + $content->assign($key, $value); + } + return $content->printPage(); + } + + /** + * Shortcut to print a simple page for guests + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array|string $parameters Parameters for the template + * @return bool + */ + public static function printGuestPage($application, $name, $parameters = []) { + $content = new OC_Template($application, $name, $name === 'error' ? $name : 'guest'); + foreach ($parameters as $key => $value) { + $content->assign($key, $value); + } + return $content->printPage(); + } + + /** + * Print a fatal error page and terminates the script + * @param string $error_msg The error message to show + * @param string $hint An optional hint message - needs to be properly escape + * @param int $statusCode + * @suppress PhanAccessMethodInternal + */ + public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) { + if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) { + \OC_App::loadApp('theming'); + } + + + if ($error_msg === $hint) { + // If the hint is the same as the message there is no need to display it twice. + $hint = ''; + } + + http_response_code($statusCode); + try { + $content = new \OC_Template('', 'error', 'error', false); + $errors = [['error' => $error_msg, 'hint' => $hint]]; + $content->assign('errors', $errors); + $content->printPage(); + } catch (\Exception $e) { + $logger = \OC::$server->getLogger(); + $logger->error("$error_msg $hint", ['app' => 'core']); + $logger->logException($e, ['app' => 'core']); + + header('Content-Type: text/plain; charset=utf-8'); + print("$error_msg $hint"); + } + die(); + } + + /** + * print error page using Exception details + * @param Exception|Throwable $exception + * @param int $statusCode + * @return bool|string + * @suppress PhanAccessMethodInternal + */ + public static function printExceptionErrorPage($exception, $statusCode = 503) { + http_response_code($statusCode); + try { + $request = \OC::$server->getRequest(); + $content = new \OC_Template('', 'exception', 'error', false); + $content->assign('errorClass', get_class($exception)); + $content->assign('errorMsg', $exception->getMessage()); + $content->assign('errorCode', $exception->getCode()); + $content->assign('file', $exception->getFile()); + $content->assign('line', $exception->getLine()); + $content->assign('trace', $exception->getTraceAsString()); + $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false)); + $content->assign('remoteAddr', $request->getRemoteAddress()); + $content->assign('requestID', $request->getId()); + $content->printPage(); + } catch (\Exception $e) { + try { + $logger = \OC::$server->getLogger(); + $logger->logException($exception, ['app' => 'core']); + $logger->logException($e, ['app' => 'core']); + } catch (Throwable $e) { + // no way to log it properly - but to avoid a white page of death we send some output + header('Content-Type: text/plain; charset=utf-8'); + print("Internal Server Error\n\n"); + print("The server encountered an internal error and was unable to complete your request.\n"); + print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); + print("More details can be found in the server log.\n"); + + // and then throw it again to log it at least to the web server error log + throw $e; + } + + header('Content-Type: text/plain; charset=utf-8'); + print("Internal Server Error\n\n"); + print("The server encountered an internal error and was unable to complete your request.\n"); + print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); + print("More details can be found in the server log.\n"); + } + die(); + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_User.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_User.php new file mode 100644 index 0000000..29c78da --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_User.php @@ -0,0 +1,417 @@ + + * @author Andreas Fischer + * @author Arthur Schiwon + * @author Bartek Przybylski + * @author Björn Schießle + * @author Christoph Wurst + * @author Georg Ehrke + * @author Jakob Sack + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author shkdee + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OCP\ILogger; + +/** + * This class provides wrapper methods for user management. Multiple backends are + * supported. User management operations are delegated to the configured backend for + * execution. + * + * Note that &run is deprecated and won't work anymore. + * + * Hooks provided: + * pre_createUser(&run, uid, password) + * post_createUser(uid, password) + * pre_deleteUser(&run, uid) + * post_deleteUser(uid) + * pre_setPassword(&run, uid, password, recoveryPassword) + * post_setPassword(uid, password, recoveryPassword) + * pre_login(&run, uid, password) + * post_login(uid) + * logout() + */ +class OC_User { + private static $_usedBackends = []; + + private static $_setupedBackends = []; + + // bool, stores if a user want to access a resource anonymously, e.g if they open a public link + private static $incognitoMode = false; + + /** + * Adds the backend to the list of used backends + * + * @param string|\OCP\UserInterface $backend default: database The backend to use for user management + * @return bool + * + * Set the User Authentication Module + * @suppress PhanDeprecatedFunction + */ + public static function useBackend($backend = 'database') { + if ($backend instanceof \OCP\UserInterface) { + self::$_usedBackends[get_class($backend)] = $backend; + \OC::$server->getUserManager()->registerBackend($backend); + } else { + // You'll never know what happens + if (null === $backend or !is_string($backend)) { + $backend = 'database'; + } + + // Load backend + switch ($backend) { + case 'database': + case 'mysql': + case 'sqlite': + \OCP\Util::writeLog('core', 'Adding user backend ' . $backend . '.', ILogger::DEBUG); + self::$_usedBackends[$backend] = new \OC\User\Database(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + case 'dummy': + self::$_usedBackends[$backend] = new \Test\Util\User\Dummy(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + default: + \OCP\Util::writeLog('core', 'Adding default user backend ' . $backend . '.', ILogger::DEBUG); + $className = 'OC_USER_' . strtoupper($backend); + self::$_usedBackends[$backend] = new $className(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + } + } + return true; + } + + /** + * remove all used backends + */ + public static function clearBackends() { + self::$_usedBackends = []; + \OC::$server->getUserManager()->clearBackends(); + } + + /** + * setup the configured backends in config.php + * @suppress PhanDeprecatedFunction + */ + public static function setupBackends() { + OC_App::loadApps(['prelogin']); + $backends = \OC::$server->getSystemConfig()->getValue('user_backends', []); + if (isset($backends['default']) && !$backends['default']) { + // clear default backends + self::clearBackends(); + } + foreach ($backends as $i => $config) { + if (!is_array($config)) { + continue; + } + $class = $config['class']; + $arguments = $config['arguments']; + if (class_exists($class)) { + if (array_search($i, self::$_setupedBackends) === false) { + // make a reflection object + $reflectionObj = new ReflectionClass($class); + + // use Reflection to create a new instance, using the $args + $backend = $reflectionObj->newInstanceArgs($arguments); + self::useBackend($backend); + self::$_setupedBackends[] = $i; + } else { + \OCP\Util::writeLog('core', 'User backend ' . $class . ' already initialized.', ILogger::DEBUG); + } + } else { + \OCP\Util::writeLog('core', 'User backend ' . $class . ' not found.', ILogger::ERROR); + } + } + } + + /** + * Try to login a user, assuming authentication + * has already happened (e.g. via Single Sign On). + * + * Log in a user and regenerate a new session. + * + * @param \OCP\Authentication\IApacheBackend $backend + * @return bool + */ + public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) { + $uid = $backend->getCurrentUserId(); + $run = true; + OC_Hook::emit("OC_User", "pre_login", ["run" => &$run, "uid" => $uid, 'backend' => $backend]); + + if ($uid) { + if (self::getUser() !== $uid) { + self::setUserId($uid); + $userSession = \OC::$server->getUserSession(); + $userSession->setLoginName($uid); + $request = OC::$server->getRequest(); + $userSession->createSessionToken($request, $uid, $uid); + // setup the filesystem + OC_Util::setupFS($uid); + // first call the post_login hooks, the login-process needs to be + // completed before we can safely create the users folder. + // For example encryption needs to initialize the users keys first + // before we can create the user folder with the skeleton files + OC_Hook::emit( + 'OC_User', + 'post_login', + [ + 'uid' => $uid, + 'password' => '', + 'isTokenLogin' => false, + ] + ); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($uid); + } + return true; + } + return false; + } + + /** + * Verify with Apache whether user is authenticated. + * + * @return boolean|null + * true: authenticated + * false: not authenticated + * null: not handled / no backend available + */ + public static function handleApacheAuth() { + $backend = self::findFirstActiveUsedBackend(); + if ($backend) { + OC_App::loadApps(); + + //setup extra user backends + self::setupBackends(); + \OC::$server->getUserSession()->unsetMagicInCookie(); + + return self::loginWithApache($backend); + } + + return null; + } + + + /** + * Sets user id for session and triggers emit + * + * @param string $uid + */ + public static function setUserId($uid) { + $userSession = \OC::$server->getUserSession(); + $userManager = \OC::$server->getUserManager(); + if ($user = $userManager->get($uid)) { + $userSession->setUser($user); + } else { + \OC::$server->getSession()->set('user_id', $uid); + } + } + + /** + * Check if the user is logged in, considers also the HTTP basic credentials + * + * @deprecated use \OC::$server->getUserSession()->isLoggedIn() + * @return bool + */ + public static function isLoggedIn() { + return \OC::$server->getUserSession()->isLoggedIn(); + } + + /** + * set incognito mode, e.g. if a user wants to open a public link + * + * @param bool $status + */ + public static function setIncognitoMode($status) { + self::$incognitoMode = $status; + } + + /** + * get incognito mode status + * + * @return bool + */ + public static function isIncognitoMode() { + return self::$incognitoMode; + } + + /** + * Returns the current logout URL valid for the currently logged-in user + * + * @param \OCP\IURLGenerator $urlGenerator + * @return string + */ + public static function getLogoutUrl(\OCP\IURLGenerator $urlGenerator) { + $backend = self::findFirstActiveUsedBackend(); + if ($backend) { + return $backend->getLogoutUrl(); + } + + $user = \OC::$server->getUserSession()->getUser(); + if ($user instanceof \OCP\IUser) { + $backend = $user->getBackend(); + if ($backend instanceof \OCP\User\Backend\ICustomLogout) { + return $backend->getLogoutUrl(); + } + } + + $logoutUrl = $urlGenerator->linkToRoute('core.login.logout'); + $logoutUrl .= '?requesttoken=' . urlencode(\OCP\Util::callRegister()); + + return $logoutUrl; + } + + /** + * Check if the user is an admin user + * + * @param string $uid uid of the admin + * @return bool + */ + public static function isAdminUser($uid) { + $group = \OC::$server->getGroupManager()->get('admin'); + $user = \OC::$server->getUserManager()->get($uid); + if ($group && $user && $group->inGroup($user) && self::$incognitoMode === false) { + return true; + } + return false; + } + + + /** + * get the user id of the user currently logged in. + * + * @return string|bool uid or false + */ + public static function getUser() { + $uid = \OC::$server->getSession() ? \OC::$server->getSession()->get('user_id') : null; + if (!is_null($uid) && self::$incognitoMode === false) { + return $uid; + } else { + return false; + } + } + + /** + * get the display name of the user currently logged in. + * + * @param string $uid + * @return string|bool uid or false + * @deprecated 8.1.0 fetch \OCP\IUser (has getDisplayName()) by using method + * get() of \OCP\IUserManager - \OC::$server->getUserManager() + */ + public static function getDisplayName($uid = null) { + if ($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->getDisplayName(); + } else { + return $uid; + } + } else { + $user = \OC::$server->getUserSession()->getUser(); + if ($user) { + return $user->getDisplayName(); + } else { + return false; + } + } + } + + /** + * Set password + * + * @param string $uid The username + * @param string $password The new password + * @param string $recoveryPassword for the encryption app to reset encryption keys + * @return bool + * + * Change the password of a user + */ + public static function setPassword($uid, $password, $recoveryPassword = null) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->setPassword($password, $recoveryPassword); + } else { + return false; + } + } + + /** + * @param string $uid The username + * @return string + * + * returns the path to the users home directory + * @deprecated Use \OC::$server->getUserManager->getHome() + */ + public static function getHome($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->getHome(); + } else { + return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; + } + } + + /** + * Get a list of all users display name + * + * @param string $search + * @param int $limit + * @param int $offset + * @return array associative array with all display names (value) and corresponding uids (key) + * + * Get a list of all display names and user ids. + * @deprecated Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead. + */ + public static function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = []; + $users = \OC::$server->getUserManager()->searchDisplayName($search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user->getUID()] = $user->getDisplayName(); + } + return $displayNames; + } + + /** + * Returns the first active backend from self::$_usedBackends. + * + * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend + */ + private static function findFirstActiveUsedBackend() { + foreach (self::$_usedBackends as $backend) { + if ($backend instanceof OCP\Authentication\IApacheBackend) { + if ($backend->isSessionActive()) { + return $backend; + } + } + } + + return null; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/OC_Util.php b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Util.php new file mode 100644 index 0000000..cb0eef5 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/OC_Util.php @@ -0,0 +1,1485 @@ + + * @author Bart Visscher + * @author Bernhard Posselt + * @author Birk Borkason + * @author Bjoern Schiessle + * @author Björn Schießle + * @author Brice Maron + * @author Christopher Schäpers + * @author Christoph Wurst + * @author Clark Tomlinson + * @author cmeh + * @author Eric Masseran + * @author Felix Epp + * @author Florin Peter + * @author Frank Karlitschek + * @author Georg Ehrke + * @author helix84 + * @author Ilja Neumann + * @author Individual IT Services + * @author Jakob Sack + * @author Joas Schilling + * @author John Molakvoæ (skjnldsv) + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Kawohl + * @author Lukas Reschke + * @author Markus Goetz + * @author Martin Mattel + * @author Marvin Thomas Rabe + * @author Michael Gapczynski + * @author Morris Jobke + * @author rakekniven + * @author Robert Dailey + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sebastian Wessalowski + * @author Stefan Rado + * @author Stefan Weil + * @author Thomas Müller + * @author Thomas Tanghus + * @author Victor Dubiniuk + * @author Vincent Petry + * @author Volkan Gezer + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\AppFramework\Http\Request; +use OC\Files\Storage\LocalRootStorage; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; + +class OC_Util { + public static $scripts = []; + public static $styles = []; + public static $headers = []; + private static $rootMounted = false; + private static $fsSetup = false; + + /** @var array Local cache of version.php */ + private static $versionCache = null; + + protected static function getAppManager() { + return \OC::$server->getAppManager(); + } + + private static function initLocalStorageRootFS() { + // mount local file backend as root + $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); + //first set up the local "root" storage + \OC\Files\Filesystem::initMountManager(); + if (!self::$rootMounted) { + \OC\Files\Filesystem::mount(LocalRootStorage::class, ['datadir' => $configDataDirectory], '/'); + self::$rootMounted = true; + } + } + + /** + * mounting an object storage as the root fs will in essence remove the + * necessity of a data folder being present. + * TODO make home storage aware of this and use the object storage instead of local disk access + * + * @param array $config containing 'class' and optional 'arguments' + * @suppress PhanDeprecatedFunction + */ + private static function initObjectStoreRootFS($config) { + // check misconfiguration + if (empty($config['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + // instantiate object store implementation + $name = $config['class']; + if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { + $segments = explode('\\', $name); + OC_App::loadApp(strtolower($segments[1])); + } + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + // mount with plain / root object store implementation + $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; + + // mount object storage as root + \OC\Files\Filesystem::initMountManager(); + if (!self::$rootMounted) { + \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); + self::$rootMounted = true; + } + } + + /** + * mounting an object storage as the root fs will in essence remove the + * necessity of a data folder being present. + * + * @param array $config containing 'class' and optional 'arguments' + * @suppress PhanDeprecatedFunction + */ + private static function initObjectStoreMultibucketRootFS($config) { + // check misconfiguration + if (empty($config['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + // instantiate object store implementation + $name = $config['class']; + if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { + $segments = explode('\\', $name); + OC_App::loadApp(strtolower($segments[1])); + } + + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + // put the root FS always in first bucket for multibucket configuration + $config['arguments']['bucket'] .= '0'; + + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + // mount with plain / root object store implementation + $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; + + // mount object storage as root + \OC\Files\Filesystem::initMountManager(); + if (!self::$rootMounted) { + \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); + self::$rootMounted = true; + } + } + + /** + * Can be set up + * + * @param string $user + * @return boolean + * @description configure the initial filesystem based on the configuration + * @suppress PhanDeprecatedFunction + * @suppress PhanAccessMethodInternal + */ + public static function setupFS($user = '') { + //setting up the filesystem twice can only lead to trouble + if (self::$fsSetup) { + return false; + } + + \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem'); + + // If we are not forced to load a specific user we load the one that is logged in + if ($user === null) { + $user = ''; + } elseif ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) { + $user = OC_User::getUser(); + } + + // load all filesystem apps before, so no setup-hook gets lost + OC_App::loadApps(['filesystem']); + + // the filesystem will finish when $user is not empty, + // mark fs setup here to avoid doing the setup from loading + // OC_Filesystem + if ($user != '') { + self::$fsSetup = true; + } + + \OC\Files\Filesystem::initMountManager(); + + $prevLogging = \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); + \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) { + /** @var \OC\Files\Storage\Common $storage */ + $storage->setMountOptions($mount->getOptions()); + } + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if (!$mount->getOption('enable_sharing', true)) { + return new \OC\Files\Storage\Wrapper\PermissionsMask([ + 'storage' => $storage, + 'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE + ]); + } + return $storage; + }); + + // install storage availability wrapper, before most other wrappers + \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) { + if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { + return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); + } + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { + return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); + } + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { + // set up quota for home storages, even for other users + // which can happen when using sharing + + /** + * @var \OC\Files\Storage\Storage $storage + */ + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') + || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') + ) { + /** @var \OC\Files\Storage\Home $storage */ + if (is_object($storage->getUser())) { + $quota = OC_Util::getUserQuota($storage->getUser()); + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { + return new \OC\Files\Storage\Wrapper\Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']); + } + } + } + + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) { + /* + * Do not allow any operations that modify the storage + */ + if ($mount->getOption('readonly', false)) { + return new \OC\Files\Storage\Wrapper\PermissionsMask([ + 'storage' => $storage, + 'mask' => \OCP\Constants::PERMISSION_ALL & ~( + \OCP\Constants::PERMISSION_UPDATE | + \OCP\Constants::PERMISSION_CREATE | + \OCP\Constants::PERMISSION_DELETE + ), + ]); + } + return $storage; + }); + + OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]); + + \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); + + //check if we are using an object storage + $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); + $objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null); + + // use the same order as in ObjectHomeMountProvider + if (isset($objectStoreMultibucket)) { + self::initObjectStoreMultibucketRootFS($objectStoreMultibucket); + } elseif (isset($objectStore)) { + self::initObjectStoreRootFS($objectStore); + } else { + self::initLocalStorageRootFS(); + } + + /** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */ + $mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class); + $rootMountProviders = $mountProviderCollection->getRootMounts(); + + /** @var \OC\Files\Mount\Manager $mountManager */ + $mountManager = \OC\Files\Filesystem::getMountManager(); + foreach ($rootMountProviders as $rootMountProvider) { + $mountManager->addMount($rootMountProvider); + } + + if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) { + \OC::$server->getEventLogger()->end('setup_fs'); + return false; + } + + //if we aren't logged in, there is no use to set up the filesystem + if ($user != "") { + $userDir = '/' . $user . '/files'; + + //jail the user into his "home" directory + \OC\Files\Filesystem::init($user, $userDir); + + OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user, 'user_dir' => $userDir]); + } + \OC::$server->getEventLogger()->end('setup_fs'); + return true; + } + + /** + * check if a password is required for each public link + * + * @return boolean + * @suppress PhanDeprecatedFunction + */ + public static function isPublicLinkPasswordRequired() { + $enforcePassword = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_links_password', 'no'); + return $enforcePassword === 'yes'; + } + + /** + * check if sharing is disabled for the current user + * @param IConfig $config + * @param IGroupManager $groupManager + * @param IUser|null $user + * @return bool + */ + public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) { + if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { + $groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $excludedGroups = json_decode($groupsList); + if (is_null($excludedGroups)) { + $excludedGroups = explode(',', $groupsList); + $newValue = json_encode($excludedGroups); + $config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); + } + $usersGroups = $groupManager->getUserGroupIds($user); + if (!empty($usersGroups)) { + $remainingGroups = array_diff($usersGroups, $excludedGroups); + // if the user is only in groups which are disabled for sharing then + // sharing is also disabled for the user + if (empty($remainingGroups)) { + return true; + } + } + } + return false; + } + + /** + * check if share API enforces a default expire date + * + * @return boolean + * @suppress PhanDeprecatedFunction + */ + public static function isDefaultExpireDateEnforced() { + $isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no'); + $enforceDefaultExpireDate = false; + if ($isDefaultExpireDateEnabled === 'yes') { + $value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no'); + $enforceDefaultExpireDate = $value === 'yes'; + } + + return $enforceDefaultExpireDate; + } + + /** + * Get the quota of a user + * + * @param IUser|null $user + * @return float Quota bytes + */ + public static function getUserQuota(?IUser $user) { + if (is_null($user)) { + return \OCP\Files\FileInfo::SPACE_UNLIMITED; + } + $userQuota = $user->getQuota(); + if ($userQuota === 'none') { + return \OCP\Files\FileInfo::SPACE_UNLIMITED; + } + return OC_Helper::computerFileSize($userQuota); + } + + /** + * copies the skeleton to the users /files + * + * @param string $userId + * @param \OCP\Files\Folder $userDirectory + * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\NotPermittedException + * @suppress PhanDeprecatedFunction + */ + public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { + $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); + $userLang = \OC::$server->getL10NFactory()->findLanguage(); + $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory); + + if (!file_exists($skeletonDirectory)) { + $dialectStart = strpos($userLang, '_'); + if ($dialectStart !== false) { + $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory); + } + if ($dialectStart === false || !file_exists($skeletonDirectory)) { + $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory); + } + if (!file_exists($skeletonDirectory)) { + $skeletonDirectory = ''; + } + } + + $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', ''); + + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + $appdata = 'appdata_' . $instanceId; + if ($userId === $appdata) { + throw new \RuntimeException('username is reserved name: ' . $appdata); + } + + if (!empty($skeletonDirectory)) { + \OCP\Util::writeLog( + 'files_skeleton', + 'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), + ILogger::DEBUG + ); + self::copyr($skeletonDirectory, $userDirectory); + // update the file cache + $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); + } + } + + /** + * copies a directory recursively by using streams + * + * @param string $source + * @param \OCP\Files\Folder $target + * @return void + */ + public static function copyr($source, \OCP\Files\Folder $target) { + $logger = \OC::$server->getLogger(); + + // Verify if folder exists + $dir = opendir($source); + if ($dir === false) { + $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']); + return; + } + + // Copy the files + while (false !== ($file = readdir($dir))) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if (is_dir($source . '/' . $file)) { + $child = $target->newFolder($file); + self::copyr($source . '/' . $file, $child); + } else { + $child = $target->newFile($file); + $sourceStream = fopen($source . '/' . $file, 'r'); + if ($sourceStream === false) { + $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']); + closedir($dir); + return; + } + stream_copy_to_stream($sourceStream, $child->fopen('w')); + } + } + } + closedir($dir); + } + + /** + * @return void + * @suppress PhanUndeclaredMethod + */ + public static function tearDownFS() { + \OC\Files\Filesystem::tearDown(); + \OC::$server->getRootFolder()->clearCache(); + self::$fsSetup = false; + self::$rootMounted = false; + } + + /** + * get the current installed version of ownCloud + * + * @return array + */ + public static function getVersion() { + OC_Util::loadVersion(); + return self::$versionCache['OC_Version']; + } + + /** + * get the current installed version string of ownCloud + * + * @return string + */ + public static function getVersionString() { + OC_Util::loadVersion(); + return self::$versionCache['OC_VersionString']; + } + + /** + * @deprecated the value is of no use anymore + * @return string + */ + public static function getEditionString() { + return ''; + } + + /** + * @description get the update channel of the current installed of ownCloud. + * @return string + */ + public static function getChannel() { + OC_Util::loadVersion(); + return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']); + } + + /** + * @description get the build number of the current installed of ownCloud. + * @return string + */ + public static function getBuild() { + OC_Util::loadVersion(); + return self::$versionCache['OC_Build']; + } + + /** + * @description load the version.php into the session as cache + * @suppress PhanUndeclaredVariable + */ + private static function loadVersion() { + if (self::$versionCache !== null) { + return; + } + + $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); + require OC::$SERVERROOT . '/version.php'; + /** @var int $timestamp */ + self::$versionCache['OC_Version_Timestamp'] = $timestamp; + /** @var string $OC_Version */ + self::$versionCache['OC_Version'] = $OC_Version; + /** @var string $OC_VersionString */ + self::$versionCache['OC_VersionString'] = $OC_VersionString; + /** @var string $OC_Build */ + self::$versionCache['OC_Build'] = $OC_Build; + + /** @var string $OC_Channel */ + self::$versionCache['OC_Channel'] = $OC_Channel; + } + + /** + * generates a path for JS/CSS files. If no application is provided it will create the path for core. + * + * @param string $application application to get the files from + * @param string $directory directory within this application (css, js, vendor, etc) + * @param string $file the file inside of the above folder + * @return string the path + */ + private static function generatePath($application, $directory, $file) { + if (is_null($file)) { + $file = $application; + $application = ""; + } + if (!empty($application)) { + return "$application/$directory/$file"; + } else { + return "$directory/$file"; + } + } + + /** + * add a javascript file + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Script to the beginning of the list + * @return void + */ + public static function addScript($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'js', $file); + + // core js files need separate handling + if ($application !== 'core' && $file !== null) { + self::addTranslations($application); + } + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a javascript file from the vendor sub folder + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Script to the beginning of the list + * @return void + */ + public static function addVendorScript($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'vendor', $file); + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a translation JS file + * + * @param string $application application id + * @param string|null $languageCode language code, defaults to the current language + * @param bool|null $prepend prepend the Script to the beginning of the list + */ + public static function addTranslations($application, $languageCode = null, $prepend = false) { + if (is_null($languageCode)) { + $languageCode = \OC::$server->getL10NFactory()->findLanguage($application); + } + if (!empty($application)) { + $path = "$application/l10n/$languageCode"; + } else { + $path = "l10n/$languageCode"; + } + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a css file + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Style to the beginning of the list + * @return void + */ + public static function addStyle($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'css', $file); + self::addExternalResource($application, $prepend, $path, "style"); + } + + /** + * add a css file from the vendor sub folder + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Style to the beginning of the list + * @return void + */ + public static function addVendorStyle($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'vendor', $file); + self::addExternalResource($application, $prepend, $path, "style"); + } + + /** + * add an external resource css/js file + * + * @param string $application application id + * @param bool $prepend prepend the file to the beginning of the list + * @param string $path + * @param string $type (script or style) + * @return void + */ + private static function addExternalResource($application, $prepend, $path, $type = "script") { + if ($type === "style") { + if (!in_array($path, self::$styles)) { + if ($prepend === true) { + array_unshift(self::$styles, $path); + } else { + self::$styles[] = $path; + } + } + } elseif ($type === "script") { + if (!in_array($path, self::$scripts)) { + if ($prepend === true) { + array_unshift(self::$scripts, $path); + } else { + self::$scripts [] = $path; + } + } + } + } + + /** + * Add a custom element to the header + * If $text is null then the element will be written as empty element. + * So use "" to get a closing tag. + * @param string $tag tag name of the element + * @param array $attributes array of attributes for the element + * @param string $text the text content for the element + * @param bool $prepend prepend the header to the beginning of the list + */ + public static function addHeader($tag, $attributes, $text = null, $prepend = false) { + $header = [ + 'tag' => $tag, + 'attributes' => $attributes, + 'text' => $text + ]; + if ($prepend === true) { + array_unshift(self::$headers, $header); + } else { + self::$headers[] = $header; + } + } + + /** + * check if the current server configuration is suitable for ownCloud + * + * @param \OC\SystemConfig $config + * @return array arrays with error messages and hints + */ + public static function checkServer(\OC\SystemConfig $config) { + $l = \OC::$server->getL10N('lib'); + $errors = []; + $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data'); + + if (!self::needUpgrade($config) && $config->getValue('installed', false)) { + // this check needs to be done every time + $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY); + } + + // Assume that if checkServer() succeeded before in this session, then all is fine. + if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) { + return $errors; + } + + $webServerRestart = false; + $setup = new \OC\Setup( + $config, + \OC::$server->get(IniGetWrapper::class), + \OC::$server->getL10N('lib'), + \OC::$server->query(\OCP\Defaults::class), + \OC::$server->getLogger(), + \OC::$server->getSecureRandom(), + \OC::$server->query(\OC\Installer::class) + ); + + $urlGenerator = \OC::$server->getURLGenerator(); + + $availableDatabases = $setup->getSupportedDatabases(); + if (empty($availableDatabases)) { + $errors[] = [ + 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'), + 'hint' => '' //TODO: sane hint + ]; + $webServerRestart = true; + } + + // Check if config folder is writable. + if (!OC_Helper::isReadOnlyConfigEnabled()) { + if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) { + $errors[] = [ + 'error' => $l->t('Cannot write into "config" directory'), + 'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s', + [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. ' + . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s', + [ $urlGenerator->linkToDocs('admin-config') ]) + ]; + } + } + + // Check if there is a writable install folder. + if ($config->getValue('appstoreenabled', true)) { + if (OC_App::getInstallPath() === null + || !is_writable(OC_App::getInstallPath()) + || !is_readable(OC_App::getInstallPath()) + ) { + $errors[] = [ + 'error' => $l->t('Cannot write into "apps" directory'), + 'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory' + . ' or disabling the appstore in the config file.') + ]; + } + } + // Create root dir. + if ($config->getValue('installed', false)) { + if (!is_dir($CONFIG_DATADIRECTORY)) { + $success = @mkdir($CONFIG_DATADIRECTORY); + if ($success) { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } else { + $errors[] = [ + 'error' => $l->t('Cannot create "data" directory'), + 'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s', + [$urlGenerator->linkToDocs('admin-dir_permissions')]) + ]; + } + } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { + // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists. + $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_')); + $handle = fopen($testFile, 'w'); + if (!$handle || fwrite($handle, 'Test write operation') === false) { + $permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.', + [$urlGenerator->linkToDocs('admin-dir_permissions')]); + $errors[] = [ + 'error' => 'Your data directory is not writable', + 'hint' => $permissionsHint + ]; + } else { + fclose($handle); + unlink($testFile); + } + } else { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } + } + + if (!OC_Util::isSetLocaleWorking()) { + $errors[] = [ + 'error' => $l->t('Setting locale to %s failed', + ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/' + . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']), + 'hint' => $l->t('Please install one of these locales on your system and restart your webserver.') + ]; + } + + // Contains the dependencies that should be checked against + // classes = class_exists + // functions = function_exists + // defined = defined + // ini = ini_get + // If the dependency is not found the missing module name is shown to the EndUser + // When adding new checks always verify that they pass on Travis as well + // for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini + $dependencies = [ + 'classes' => [ + 'ZipArchive' => 'zip', + 'DOMDocument' => 'dom', + 'XMLWriter' => 'XMLWriter', + 'XMLReader' => 'XMLReader', + ], + 'functions' => [ + 'xml_parser_create' => 'libxml', + 'mb_strcut' => 'mbstring', + 'ctype_digit' => 'ctype', + 'json_encode' => 'JSON', + 'gd_info' => 'GD', + 'gzencode' => 'zlib', + 'iconv' => 'iconv', + 'simplexml_load_string' => 'SimpleXML', + 'hash' => 'HASH Message Digest Framework', + 'curl_init' => 'cURL', + 'openssl_verify' => 'OpenSSL', + ], + 'defined' => [ + 'PDO::ATTR_DRIVER_NAME' => 'PDO' + ], + 'ini' => [ + 'default_charset' => 'UTF-8', + ], + ]; + $missingDependencies = []; + $invalidIniSettings = []; + + $iniWrapper = \OC::$server->get(IniGetWrapper::class); + foreach ($dependencies['classes'] as $class => $module) { + if (!class_exists($class)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['functions'] as $function => $module) { + if (!function_exists($function)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['defined'] as $defined => $module) { + if (!defined($defined)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['ini'] as $setting => $expected) { + if (is_bool($expected)) { + if ($iniWrapper->getBool($setting) !== $expected) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + if (is_int($expected)) { + if ($iniWrapper->getNumeric($setting) !== $expected) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + if (is_string($expected)) { + if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + } + + foreach ($missingDependencies as $missingDependency) { + $errors[] = [ + 'error' => $l->t('PHP module %s not installed.', [$missingDependency]), + 'hint' => $l->t('Please ask your server administrator to install the module.'), + ]; + $webServerRestart = true; + } + foreach ($invalidIniSettings as $setting) { + if (is_bool($setting[1])) { + $setting[1] = $setting[1] ? 'on' : 'off'; + } + $errors[] = [ + 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]), + 'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again') + ]; + $webServerRestart = true; + } + + /** + * The mbstring.func_overload check can only be performed if the mbstring + * module is installed as it will return null if the checking setting is + * not available and thus a check on the boolean value fails. + * + * TODO: Should probably be implemented in the above generic dependency + * check somehow in the long-term. + */ + if ($iniWrapper->getBool('mbstring.func_overload') !== null && + $iniWrapper->getBool('mbstring.func_overload') === true) { + $errors[] = [ + 'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]), + 'hint' => $l->t('To fix this issue set mbstring.func_overload to 0 in your php.ini') + ]; + } + + if (function_exists('xml_parser_create') && + LIBXML_LOADED_VERSION < 20700) { + $version = LIBXML_LOADED_VERSION; + $major = floor($version/10000); + $version -= ($major * 10000); + $minor = floor($version/100); + $version -= ($minor * 100); + $patch = $version; + $errors[] = [ + 'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]), + 'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.') + ]; + } + + if (!self::isAnnotationsWorking()) { + $errors[] = [ + 'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'), + 'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.') + ]; + } + + if (!\OC::$CLI && $webServerRestart) { + $errors[] = [ + 'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'), + 'hint' => $l->t('Please ask your server administrator to restart the web server.') + ]; + } + + $errors = array_merge($errors, self::checkDatabaseVersion()); + + // Cache the result of this function + \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0); + + return $errors; + } + + /** + * Check the database version + * + * @return array errors array + */ + public static function checkDatabaseVersion() { + $l = \OC::$server->getL10N('lib'); + $errors = []; + $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite'); + if ($dbType === 'pgsql') { + // check PostgreSQL version + try { + $result = \OC_DB::executeAudited('SHOW SERVER_VERSION'); + $data = $result->fetchRow(); + if (isset($data['server_version'])) { + $version = $data['server_version']; + if (version_compare($version, '9.0.0', '<')) { + $errors[] = [ + 'error' => $l->t('PostgreSQL >= 9 required'), + 'hint' => $l->t('Please upgrade your database version') + ]; + } + } + } catch (\Doctrine\DBAL\DBALException $e) { + $logger = \OC::$server->getLogger(); + $logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9'); + $logger->logException($e); + } + } + return $errors; + } + + /** + * Check for correct file permissions of data directory + * + * @param string $dataDirectory + * @return array arrays with error messages and hints + */ + public static function checkDataDirectoryPermissions($dataDirectory) { + if (\OC::$server->getConfig()->getSystemValue('check_data_directory_permissions', true) === false) { + return []; + } + + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, -1) !== '0') { + chmod($dataDirectory, 0770); + clearstatcache(); + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if ($perms[2] !== '0') { + $l = \OC::$server->getL10N('lib'); + return [[ + 'error' => $l->t('Your data directory is readable by other users'), + 'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other users.'), + ]]; + } + } + return []; + } + + /** + * Check that the data directory exists and is valid by + * checking the existence of the ".ocdata" file. + * + * @param string $dataDirectory data directory path + * @return array errors found + */ + public static function checkDataDirectoryValidity($dataDirectory) { + $l = \OC::$server->getL10N('lib'); + $errors = []; + if ($dataDirectory[0] !== '/') { + $errors[] = [ + 'error' => $l->t('Your data directory must be an absolute path'), + 'hint' => $l->t('Check the value of "datadirectory" in your configuration') + ]; + } + if (!file_exists($dataDirectory . '/.ocdata')) { + $errors[] = [ + 'error' => $l->t('Your data directory is invalid'), + 'hint' => $l->t('Ensure there is a file called ".ocdata"' . + ' in the root of the data directory.') + ]; + } + return $errors; + } + + /** + * Check if the user is logged in, redirects to home if not. With + * redirect URL parameter to the request URI. + * + * @return void + */ + public static function checkLoggedIn() { + // Check if we are a user + if (!\OC::$server->getUserSession()->isLoggedIn()) { + header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute( + 'core.login.showLoginForm', + [ + 'redirect_url' => \OC::$server->getRequest()->getRequestUri(), + ] + ) + ); + exit(); + } + // Redirect to 2FA challenge selection if 2FA challenge was not solved yet + if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) { + header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge')); + exit(); + } + } + + /** + * Check if the user is a admin, redirects to home if not + * + * @return void + */ + public static function checkAdminUser() { + OC_Util::checkLoggedIn(); + if (!OC_User::isAdminUser(OC_User::getUser())) { + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); + exit(); + } + } + + /** + * Returns the URL of the default page + * based on the system configuration and + * the apps visible for the current user + * + * @return string URL + * @suppress PhanDeprecatedFunction + */ + public static function getDefaultPageUrl() { + /** @var IConfig $config */ + $config = \OC::$server->get(IConfig::class); + $urlGenerator = \OC::$server->getURLGenerator(); + // Deny the redirect if the URL contains a @ + // This prevents unvalidated redirects like ?redirect_url=:user@domain.com + if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) { + $location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url'])); + } else { + $defaultPage = \OC::$server->getConfig()->getAppValue('core', 'defaultpage'); + if ($defaultPage) { + $location = $urlGenerator->getAbsoluteURL($defaultPage); + } else { + $appId = 'files'; + $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard,files')); + + /** @var IUserSession $userSession */ + $userSession = \OC::$server->get(IUserSession::class); + $user = $userSession->getUser(); + if ($user) { + $userDefaultApps = explode(',', $config->getUserValue($user->getUID(), 'core', 'defaultapp')); + $defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps)); + } + + // find the first app that is enabled for the current user + foreach ($defaultApps as $defaultApp) { + $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp)); + if (static::getAppManager()->isEnabledForUser($defaultApp)) { + $appId = $defaultApp; + break; + } + } + + if ($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') { + $location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/'); + } else { + $location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/'); + } + } + } + return $location; + } + + /** + * Redirect to the user default page + * + * @return void + */ + public static function redirectToDefaultPage() { + $location = self::getDefaultPageUrl(); + header('Location: ' . $location); + exit(); + } + + /** + * get an id unique for this instance + * + * @return string + */ + public static function getInstanceId() { + $id = \OC::$server->getSystemConfig()->getValue('instanceid', null); + if (is_null($id)) { + // We need to guarantee at least one letter in instanceid so it can be used as the session_name + $id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS); + \OC::$server->getSystemConfig()->setValue('instanceid', $id); + } + return $id; + } + + /** + * Public function to sanitize HTML + * + * This function is used to sanitize HTML and should be applied on any + * string or array of strings before displaying it on a web page. + * + * @param string|array $value + * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter. + */ + public static function sanitizeHTML($value) { + if (is_array($value)) { + $value = array_map(function ($value) { + return self::sanitizeHTML($value); + }, $value); + } else { + // Specify encoding for PHP<5.4 + $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + } + return $value; + } + + /** + * Public function to encode url parameters + * + * This function is used to encode path to file before output. + * Encoding is done according to RFC 3986 with one exception: + * Character '/' is preserved as is. + * + * @param string $component part of URI to encode + * @return string + */ + public static function encodePath($component) { + $encoded = rawurlencode($component); + $encoded = str_replace('%2F', '/', $encoded); + return $encoded; + } + + + public function createHtaccessTestFile(\OCP\IConfig $config) { + // php dev server does not support htaccess + if (php_sapi_name() === 'cli-server') { + return false; + } + + // testdata + $fileName = '/htaccesstest.txt'; + $testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.'; + + // creating a test file + $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName; + + if (file_exists($testFile)) {// already running this test, possible recursive call + return false; + } + + $fp = @fopen($testFile, 'w'); + if (!$fp) { + throw new OC\HintException('Can\'t create test file to check for working .htaccess file.', + 'Make sure it is possible for the webserver to write to ' . $testFile); + } + fwrite($fp, $testContent); + fclose($fp); + + return $testContent; + } + + /** + * Check if the .htaccess file is working + * @param \OCP\IConfig $config + * @return bool + * @throws Exception + * @throws \OC\HintException If the test file can't get written. + */ + public function isHtaccessWorking(\OCP\IConfig $config) { + if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) { + return true; + } + + $testContent = $this->createHtaccessTestFile($config); + if ($testContent === false) { + return false; + } + + $fileName = '/htaccesstest.txt'; + $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName; + + // accessing the file via http + $url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName); + try { + $content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody(); + } catch (\Exception $e) { + $content = false; + } + + if (strpos($url, 'https:') === 0) { + $url = 'http:' . substr($url, 6); + } else { + $url = 'https:' . substr($url, 5); + } + + try { + $fallbackContent = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody(); + } catch (\Exception $e) { + $fallbackContent = false; + } + + // cleanup + @unlink($testFile); + + /* + * If the content is not equal to test content our .htaccess + * is working as required + */ + return $content !== $testContent && $fallbackContent !== $testContent; + } + + /** + * Check if the setlocal call does not work. This can happen if the right + * local packages are not available on the server. + * + * @return bool + */ + public static function isSetLocaleWorking() { + \Patchwork\Utf8\Bootup::initLocale(); + if ('' === basename('§')) { + return false; + } + return true; + } + + /** + * Check if it's possible to get the inline annotations + * + * @return bool + */ + public static function isAnnotationsWorking() { + $reflection = new \ReflectionMethod(__METHOD__); + $docs = $reflection->getDocComment(); + + return (is_string($docs) && strlen($docs) > 50); + } + + /** + * Check if the PHP module fileinfo is loaded. + * + * @return bool + */ + public static function fileInfoLoaded() { + return function_exists('finfo_open'); + } + + /** + * clear all levels of output buffering + * + * @return void + */ + public static function obEnd() { + while (ob_get_level()) { + ob_end_clean(); + } + } + + /** + * Checks whether the server is running on Mac OS X + * + * @return bool true if running on Mac OS X, false otherwise + */ + public static function runningOnMac() { + return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN'); + } + + /** + * Handles the case that there may not be a theme, then check if a "default" + * theme exists and take that one + * + * @return string the theme + */ + public static function getTheme() { + $theme = \OC::$server->getSystemConfig()->getValue("theme", ''); + + if ($theme === '') { + if (is_dir(OC::$SERVERROOT . '/themes/default')) { + $theme = 'default'; + } + } + + return $theme; + } + + /** + * Normalize a unicode string + * + * @param string $value a not normalized string + * @return bool|string + */ + public static function normalizeUnicode($value) { + if (Normalizer::isNormalized($value)) { + return $value; + } + + $normalizedValue = Normalizer::normalize($value); + if ($normalizedValue === null || $normalizedValue === false) { + \OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']); + return $value; + } + + return $normalizedValue; + } + + /** + * A human readable string is generated based on version and build number + * + * @return string + */ + public static function getHumanVersion() { + $version = OC_Util::getVersionString(); + $build = OC_Util::getBuild(); + if (!empty($build) and OC_Util::getChannel() === 'daily') { + $version .= ' Build:' . $build; + } + return $version; + } + + /** + * Returns whether the given file name is valid + * + * @param string $file file name to check + * @return bool true if the file name is valid, false otherwise + * @deprecated use \OC\Files\View::verifyPath() + */ + public static function isValidFileName($file) { + $trimmed = trim($file); + if ($trimmed === '') { + return false; + } + if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { + return false; + } + + // detect part files + if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) { + return false; + } + + foreach (str_split($trimmed) as $char) { + if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) { + return false; + } + } + return true; + } + + /** + * Check whether the instance needs to perform an upgrade, + * either when the core version is higher or any app requires + * an upgrade. + * + * @param \OC\SystemConfig $config + * @return bool whether the core or any app needs an upgrade + * @throws \OC\HintException When the upgrade from the given version is not allowed + */ + public static function needUpgrade(\OC\SystemConfig $config) { + if ($config->getValue('installed', false)) { + $installedVersion = $config->getValue('version', '0.0.0'); + $currentVersion = implode('.', \OCP\Util::getVersion()); + $versionDiff = version_compare($currentVersion, $installedVersion); + if ($versionDiff > 0) { + return true; + } elseif ($config->getValue('debug', false) && $versionDiff < 0) { + // downgrade with debug + $installedMajor = explode('.', $installedVersion); + $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; + $currentMajor = explode('.', $currentVersion); + $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; + if ($installedMajor === $currentMajor) { + // Same major, allow downgrade for developers + return true; + } else { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } + } elseif ($versionDiff < 0) { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } + + // also check for upgrades for apps (independently from the user) + $apps = \OC_App::getEnabledApps(false, true); + $shouldUpgrade = false; + foreach ($apps as $app) { + if (\OC_App::shouldUpgrade($app)) { + $shouldUpgrade = true; + break; + } + } + return $shouldUpgrade; + } else { + return false; + } + } + + /** + * is this Internet explorer ? + * + * @return boolean + */ + public static function isIe() { + if (!isset($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/docker/overlays/nextcloud/html/lib/private/legacy/template/functions.php b/docker/overlays/nextcloud/html/lib/private/legacy/template/functions.php new file mode 100644 index 0000000..e2a1c47 --- /dev/null +++ b/docker/overlays/nextcloud/html/lib/private/legacy/template/functions.php @@ -0,0 +1,335 @@ + + * @author Bernhard Posselt + * @author Christoph Wurst + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Julius Härtl + * @author Lukas Reschke + * @author Michael Letzgus + * @author Morris Jobke + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Prints a sanitized string + * @param string $string the string which will be escaped and printed + */ +function p($string) { + print(\OCP\Util::sanitizeHTML($string)); +} + + +/** + * Prints a tag for loading css + * @param string $href the source URL, ignored when empty + * @param string $opts, additional optional options + */ +function emit_css_tag($href, $opts = '') { + $s='\n"); +} + +/** + * Prints all tags for CSS loading + * @param array $obj all the script information from template + */ +function emit_css_loading_tags($obj) { + foreach ($obj['cssfiles'] as $css) { + emit_css_tag($css); + } + foreach ($obj['printcssfiles'] as $css) { + emit_css_tag($css, 'media="print"'); + } +} + +/** + * Prints a '; + print_unescaped($s."\n"); +} + +/** + * Print all